Set up SSH keys – instructions
What is SSH and why use it?
SSH stands for Secure Shell. It provides an encrypted connection to another computer so you can run terminal commands there. This is useful for administering a Linux computer, server or virtual machine (VM). File transfers using SFTP or SCP also use SSH.
SSH key authentication uses two files: the private key stays on your own computer and must remain secret. The public key is installed on the target. During authentication your computer proves that it holds the matching private key without transmitting it. This script sets up that login method and a short connection name.
Prepare and run
The target needs a reachable SSH service. You need its user account and normally its account password for the initial transfer; password login must be permitted for that transfer. Locally, Bash, ssh, ssh-keygen and ssh-copy-id are required, plus sshpass for password-based transfer. The script does not set up an SSH server or install packages.
Save the script in a writable folder and open it in a text editor. Adjust SSH_USER, SSH_HOST and SSH_ALIAS. There are two password options:
- Enter it in the terminal: Set
SSH_PASSWORT="". The script prompts without displaying the password when needed. - Enter it in the script: Replace the placeholder in
SSH_PASSWORTwith the target account password. After setup, remove it from the file and save the file withSSH_PASSWORT="". The script does not remove it from the saved file automatically.
A directly entered password is stored as plain text in the file. For shell special characters such as $, backticks or quotation marks, the hidden terminal prompt is simpler because no Bash escaping is needed.
These values are an example using the terminal prompt:
SSH_USER="josef"
SSH_HOST="192.168.1.50"
SSH_ALIAS="laborvm"
SSH_PASSWORT=""Open a terminal in the script folder and run it as your normal user. When using bash, you do not need to make the file executable with chmod +x first.
bash ./ssh_schluessel_einrichten.sh --help
bash ./ssh_schluessel_einrichten.sh --version
bash ./ssh_schluessel_einrichten.shThe script step by step
The following sections reproduce the executable part of script version 1.0.2 in its original order. The long opening comment containing the description and change history is omitted. These blocks explain the code; run the complete downloaded file with your own target settings.
1. Bash and error handling
The first line selects Bash. set -u treats references to unset variables as errors. pipefail makes a failure inside a pipeline visible in its exit status. These options do not enable automatic termination after every failed command; the script explicitly checks important operations and uses die to stop.
#!/usr/bin/env bash
set -u -o pipefail2. Version and target settings
The script version is 1.0.2. SSH_USER is the target account, SSH_HOST its hostname or IP address, and SSH_ALIAS a short name used both for connecting and for naming the key. The following code shows the actual file. The value of SSH_PASSWORT is placeholder text and will be treated as a password if left unchanged. You have two options: set this line to SSH_PASSWORT="" to request a required password invisibly in the terminal, or replace the placeholder with the target account’s actual password and remove it from the script file after setup. Save the file with SSH_PASSWORT="" afterwards. Clearing the runtime variable does not change the saved file.
VERSION="1.0.2"
# Für ein neues Ziel nur diese vier Werte ändern.
SSH_USER="benutzer"
SSH_HOST="rechner.local"
SSH_ALIAS="zielrechner"
# Leer lassen; ein erforderliches Passwort wird verdeckt abgefragt.
SSH_PASSWORT="pw vom zielrechner"3. Script folder, log and errors
BASH_SOURCE[0] identifies the running script. Its directory and name determine the log path: ssh_schluessel_einrichten.log beside the script. : > "$LOG_FILE" clears the log on every invocation, including help and version requests. If writing fails, the script exits with code 5. timestamp formats the time; log uses tee -a to write to both terminal and log. die logs an error and exits with the supplied code. Output from external commands is not automatically all captured in this log.
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
LOG_FILE="$SCRIPT_DIR/${SCRIPT_NAME%.sh}.log"
: > "$LOG_FILE" || { printf 'FEHLER: Logdatei nicht beschreibbar: %s\n' "$LOG_FILE" >&2; exit 5; }
timestamp() { date '+%d.%m.%Y %H:%M'; }
log() { printf '%s %s\n' "$(timestamp)" "$*" | tee -a "$LOG_FILE"; }
die() { local code="$1"; shift; log "FEHLER: $*"; exit "$code"; }4. Help and version output
show_help prints the text between the EOF markers. Quoting the opening marker prevents variable expansion in that text. case examines the first argument: --help or -h displays help, --version prints the version, and no argument starts setup. An unknown first argument produces exit code 2. ${1:-} allows the first argument to be absent despite set -u.
show_help() {
cat <<'EOF'
SSH-Schlüssel für Rechner/VM einrichten
Verwendung:
./ssh_schluessel_einrichten.sh
./ssh_schluessel_einrichten.sh --help
./ssh_schluessel_einrichten.sh --version
Das Skript prüft zuerst, ob Schlüssel, Alias und Schlüsselanmeldung bereits
funktionieren. Ist alles eingerichtet, werden keine Änderungen vorgenommen.
Für ein neues Ziel im Skript SSH_USER, SSH_HOST und SSH_ALIAS anpassen.
Ein erforderliches Passwort wird verdeckt abgefragt.
EOF
}
case "${1:-}" in
-h|--help) show_help; exit 0 ;;
--version) printf '%s\n' "$VERSION"; exit 0 ;;
"") ;;
*) show_help >&2; exit 2 ;;
esac5. Paths and startup information
$HOME is the current user’s home directory, also denoted by ~. The private key is named, for example, ~/.ssh/id_ed25519_zielrechner; its public counterpart adds .pub. CONFIG identifies the personal SSH configuration and ORIGINAL its one-time backup. Program, version, time, target and alias are logged. Run the script as the user who will later connect, without sudo.
KEY="$HOME/.ssh/id_ed25519_${SSH_ALIAS}"
CONFIG="$HOME/.ssh/config"
ORIGINAL="${CONFIG}.original"
log "PROGRAMM: $SCRIPT_NAME"
log "VERSION: $VERSION"
log "START: $(timestamp)"
log "ZIEL: ${SSH_USER}@${SSH_HOST}"
log "ALIAS: ${SSH_ALIAS}"6. Check required tools
command -v checks for ssh, ssh-keygen and ssh-copy-id. Missing tools are collected in missing. If any are absent, a Linux Mint installation suggestion is printed and the script exits with code 3. Nothing is installed automatically. sshpass is checked later only if transferring the key remains necessary.
missing=()
for cmd in ssh ssh-keygen ssh-copy-id; do
command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
done
if ((${#missing[@]})); then
{
echo "FEHLENDE VORAUSSETZUNGEN"
printf 'Fehlt: %s\n' "${missing[@]}"
echo "Vorschlag unter Linux Mint:"
echo "sudo apt install openssh-client"
echo "Es wird NICHT automatisch installiert."
} | tee -a "$LOG_FILE"
exit 3
fi7. Validate target characters
These regular expressions limit allowed characters. The alias permits letters, digits, underscores and hyphens; user and host allow the additional characters shown in the code. Spaces are not allowed. Invalid input causes exit code 2. This does not establish that the target is reachable or the account exists.
[[ "$SSH_ALIAS" =~ ^[a-zA-Z0-9_-]+$ && "$SSH_USER" =~ ^[a-zA-Z0-9_.-]+$ &&
"$SSH_HOST" =~ ^[a-zA-Z0-9_.:-]+$ ]] || die 2 "Ungültiger Alias, Benutzer oder Host."8. Check an existing SSH alias
The script searches ~/.ssh/config for a matching Host entry. ssh -G prints effective settings without logging in. awk extracts the hostname, user and first identity-file path. They must exactly match the script settings. A mismatch causes an error rather than overwriting the entry. Even an equivalent path written differently can fail this text comparison.
alias_ok=false
if [[ -f "$CONFIG" ]] && grep -Eq "^[[:space:]]*Host[[:space:]]+${SSH_ALIAS}([[:space:]]|$)" "$CONFIG"; then
effective="$(ssh -G "$SSH_ALIAS" 2>/dev/null)" || die 5 "SSH-Konfiguration ist ungültig."
actual_host="$(awk '$1 == "hostname" {print $2; exit}' <<< "$effective")"
actual_user="$(awk '$1 == "user" {print $2; exit}' <<< "$effective")"
actual_key="$(awk '$1 == "identityfile" {print $2; exit}' <<< "$effective")"
if [[ "$actual_host" != "$SSH_HOST" || "$actual_user" != "$SSH_USER" || "$actual_key" != "$KEY" ]]; then
die 5 "Alias '$SSH_ALIAS' zeigt nicht auf die eingetragenen Werte. Bitte ~/.ssh/config prüfen."
fi
alias_ok=true
fi9. Check an existing key pair
If either key file exists, both must be regular files. ssh-keygen -y derives a public key from the private one. The comparison uses the key type and key data, ignoring the trailing comment. Missing or mismatched files cause exit code 5; existing keys are not overwritten. An existing private key protected by a passphrase may request input at this step.
key_ok=false
if [[ -e "$KEY" || -e "${KEY}.pub" ]]; then
[[ -f "$KEY" && -f "${KEY}.pub" ]] || die 5 "Schlüsselpaar unvollständig; vorhandene Dateien bleiben unverändert."
derived="$(ssh-keygen -y -f "$KEY" 2>/dev/null)" || die 5 "Privater Schlüssel kann nicht geprüft werden."
[[ "$(awk '{print $1, $2}' <<< "$derived")" == "$(awk 'NR==1 {print $1, $2}' "${KEY}.pub")" ]] ||
die 5 "Privater und öffentlicher Schlüssel passen nicht zusammen."
key_ok=true
fi10. Recognise a working setup
If key and alias are present, SSH executes true on the target. This succeeds without opening an interactive shell. BatchMode=yes suppresses interactive prompts during this test; password and keyboard-interactive authentication are disabled. PreferredAuthentications=publickey requests key authentication. -i supplies the key file, IdentitiesOnly=yes restricts identities to configured ones, and ConnectTimeout=8 limits connection establishment. On success the script exits immediately, leaving keys and SSH configuration unchanged; the log has already been restarted. Failure can also indicate network, SSH-service or host-key problems.
# Zuerst ausschließlich prüfen. Bei funktionierender Einrichtung nichts ändern.
if $key_ok && $alias_ok; then
log "INFO: Schlüssel und Alias sind vorhanden."
if ssh -o BatchMode=yes \
-o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
-o PreferredAuthentications=publickey \
-o IdentitiesOnly=yes -i "$KEY" \
-o ConnectTimeout=8 \
"$SSH_ALIAS" true >/dev/null 2>&1; then
log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
log "Keine Änderung erforderlich."
exit 0
fi
log "WARNUNG: Schlüssel und Alias vorhanden, Verbindungstest fehlgeschlagen."
fi11. Generate keys and set permissions
An ED25519 key is generated only when no existing pair is available. -f sets the filename, -C adds a comment and -q reduces output. -N "" creates a private key without a passphrase: anyone obtaining that file can use it without a separate key password. Share only the .pub file. chmod 700 restricts directory access to the owner; 600 restricts private-key reading and writing to the owner. 644 allows others to read the public key but only its owner to modify it.
if ! $key_ok; then
mkdir -p "$HOME/.ssh" || die 5 "Kann $HOME/.ssh nicht erstellen."
chmod 700 "$HOME/.ssh" || die 5 "Kann Rechte von ~/.ssh nicht setzen."
log "Erzeuge ED25519-Schlüssel: $KEY"
ssh-keygen -q -t ed25519 -f "$KEY" -C "PC-${SSH_ALIAS}" -N "" ||
die 1 "SSH-Schlüssel konnte nicht erzeugt werden."
else
log "INFO: Vorhandener Schlüssel wird weiterverwendet: $KEY"
fi
[[ -f "${KEY}.pub" ]] || die 1 "Öffentlicher Schlüssel fehlt: ${KEY}.pub"
chmod 600 "$KEY" || die 5 "Kann Rechte des privaten Schlüssels nicht setzen."
chmod 644 "${KEY}.pub" || die 5 "Kann Rechte des öffentlichen Schlüssels nicht setzen."12. Add the alias and back up configuration
If the alias is missing, the configuration file is created as needed and given permissions 600. An existing non-empty configuration is copied once to config.original before appending; an existing backup is preserved and an empty configuration is not backed up. Permissions have already been adjusted by then. cat >> appends the Host block linking the alias with host, user and key. ssh -G then checks effective values again because other SSH settings can take precedence. Failure after appending does not automatically undo the change.
if ! $alias_ok; then
mkdir -p "$HOME/.ssh" || die 5 "Kann $HOME/.ssh nicht erstellen."
chmod 700 "$HOME/.ssh" || die 5 "Kann Rechte von ~/.ssh nicht setzen."
touch "$CONFIG" || die 5 "SSH-Konfiguration konnte nicht angelegt werden."
chmod 600 "$CONFIG" || die 5 "Kann Rechte der SSH-Konfiguration nicht setzen."
if [[ -s "$CONFIG" && ! -e "$ORIGINAL" ]]; then
cp -a -- "$CONFIG" "$ORIGINAL" ||
die 5 "Originalsicherung konnte nicht erstellt werden."
log "Originalsicherung erstellt: $ORIGINAL"
elif [[ -e "$ORIGINAL" ]]; then
log "INFO: $ORIGINAL existiert bereits und wird nicht überschrieben."
fi
cat >> "$CONFIG" <<EOF
Host ${SSH_ALIAS}
HostName ${SSH_HOST}
User ${SSH_USER}
IdentityFile ${KEY}
IdentitiesOnly yes
EOF
[[ $? -eq 0 ]] || die 5 "SSH-Alias konnte nicht geschrieben werden."
effective="$(ssh -G "$SSH_ALIAS" 2>/dev/null)" || die 5 "SSH-Konfiguration ist ungültig."
[[ "$(awk '$1 == "hostname" {print $2; exit}' <<< "$effective")" == "$SSH_HOST" &&
"$(awk '$1 == "user" {print $2; exit}' <<< "$effective")" == "$SSH_USER" &&
"$(awk '$1 == "identityfile" {print $2; exit}' <<< "$effective")" == "$KEY" ]] ||
die 5 "Wirksamer Alias weicht ab; bitte ~/.ssh/config prüfen."
log "SSH-Alias '${SSH_ALIAS}' wurde ergänzt."
fi13. Test again before transferring
The same key-only test is repeated with the key and alias now in place. If login already works, no password-based transfer or sshpass is needed. This may happen when the public key was previously installed on the target.
# Nach Schlüssel/Alias erneut prüfen, bevor sshpass benötigt wird.
if ssh -o BatchMode=yes \
-o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
-o PreferredAuthentications=publickey \
-o IdentitiesOnly=yes -i "$KEY" \
-o ConnectTimeout=8 \
"$SSH_ALIAS" true >/dev/null 2>&1; then
log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
log "Keine weitere Änderung erforderlich."
exit 0
fi14. Check sshpass only when needed
After a failed key test, the script checks for sshpass, which supplies a password to ssh-copy-id. If missing, an installation suggestion is printed and the script exits with code 3. Keys and configuration entries already created remain in place.
if ! command -v sshpass >/dev/null 2>&1; then
{
echo "FEHLENDE VORAUSSETZUNG"
echo "Fehlt: sshpass"
echo "Vorschlag unter Linux Mint:"
echo "sudo apt install sshpass"
echo "Es wird NICHT automatisch installiert."
} | tee -a "$LOG_FILE"
exit 3
fi15. Request a password without displaying it
This block runs only if SSH_PASSWORT is empty. -t 0 checks that standard input is a terminal. read -r -s -p reads the password literally without echoing characters and displays a prompt. Empty input or a missing terminal causes exit code 7. A password entered directly in the file skips this prompt block. The supplied placeholder also skips it, so it must be emptied or replaced with the actual password. Remove a directly entered password from the file and save it after setup.
if [[ -z "$SSH_PASSWORT" ]]; then
[[ -t 0 ]] || die 7 "Passwort nötig; bitte interaktiv im Terminal starten."
read -r -s -p "SSH-Passwort für ${SSH_USER}@${SSH_HOST}: " SSH_PASSWORT
printf '\n'
[[ -n "$SSH_PASSWORT" ]] || die 7 "Kein Passwort eingegeben."
fi16. Transfer the public key
The password is exported as SSHPASS; sshpass -e reads this environment variable, keeping it out of command-line arguments. This is not encrypted password storage. ssh-copy-id -i transfers the public key, normally adding it to the target user’s ~/.ssh/authorized_keys. The private key stays local. StrictHostKeyChecking=accept-new automatically accepts and records previously unknown host keys but rejects changed known keys. Verify the target’s identity through a trusted channel before the first transfer. This command uses user@host, not the alias. Afterwards the environment variable is removed; on success the script variable is also cleared. A transfer failure causes exit code 6.
# sshpass liest das Passwort aus der Umgebungsvariable; es erscheint nicht
# als Kommandozeilenargument.
export SSHPASS="$SSH_PASSWORT"
log "Übertrage öffentlichen Schlüssel auf den Zielrechner ..."
if ! sshpass -e ssh-copy-id \
-o StrictHostKeyChecking=accept-new \
-i "${KEY}.pub" \
"${SSH_USER}@${SSH_HOST}"; then
unset SSHPASS
die 6 "ssh-copy-id ist fehlgeschlagen."
fi
unset SSHPASS
SSH_PASSWORT=""17. Final test and future connections
Finally, key-only authentication is tested again. On success the script prints the future connection command and exits with code 0. Password authentication on the target remains enabled. If the test fails, exit code 6 is returned; earlier changes are not automatically rolled back.
log "Teste Anmeldung ausschließlich mit SSH-Schlüssel ..."
if ssh -o BatchMode=yes \
-o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
-o PreferredAuthentications=publickey \
-o IdentitiesOnly=yes -i "$KEY" \
-o ConnectTimeout=8 \
"$SSH_ALIAS" true; then
log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
log "Künftig genügt: ssh ${SSH_ALIAS}"
log "Die Passwort-Anmeldung auf dem Ziel wurde NICHT deaktiviert."
exit 0
fi
die 6 "Der abschließende SSH-Schlüsseltest ist fehlgeschlagen."After successful setup
If you entered the password directly, reopen the script in your editor, replace the password line with SSH_PASSWORT="" and save the file.
Connect using your chosen alias. For the example above, use:
ssh laborvmUse exit to leave the remote session. Working key authentication no longer requires the account password. A private key newly created by this script has no passphrase.
Files and troubleshooting
- Local: private key
~/.ssh/id_ed25519_<alias>and its public counterpart ending in.pub. - Local: alias in
~/.ssh/configand, where applicable, the one-time~/.ssh/config.originalbackup. - Local: known host keys are normally saved in
~/.ssh/known_hosts. - Beside the script:
ssh_schluessel_einrichten.log, restarted on every invocation. Save any needed error log before running again. - Target: public key normally in the target user’s
~/.ssh/authorized_keys.
Use echo $? immediately after running the script to display its exit code. 0 means success; 1 a general error, 2 invalid arguments or target settings, 3 missing tools, 5 file, permission or configuration problems, 6 an SSH/transfer failure and 7 required or missing user input.
Read the terminal message and log first. Check target address, reachability, SSH service, username and password. Inspect the SSH configuration for an existing mismatched alias and the key files for an incomplete pair. Do not hastily delete existing keys. The script uses normal SSH settings and has no dedicated input for a custom SSH port. Changes made before a later failure remain in place.
