A production-ready, idempotent bash script that simplifies SSH configuration setup and hardening on Linux systems.
# Run full setup (check + generate keys + configure)
sudo ./ssh-setup.sh all
# Generate SSH keys only
./ssh-setup.sh generate --email "you@example.com"
# Apply security hardening (requires root)
sudo ./ssh-setup.sh harden
# Harden on a custom port
sudo ./ssh-setup.sh harden --port 2222 --yes
# Preview changes without executing
./ssh-setup.sh all --dry-run
# View current configuration summary
./ssh-setup.sh status| Command | Description | Requires Root |
|---|---|---|
all |
Full workflow: check → generate → configure → status | Optional |
check |
Verify SSH installation, keys, and permissions | No |
generate |
Create Ed25519 SSH key pair | No |
add-key |
Add public key to authorized_keys (dedup-aware) | No |
harden |
Apply security hardening directives to sshd_config | Yes |
status |
Show effective sshd configuration (21 directives) | No |
help |
Display full usage documentation | No |
| Option | Description |
|---|---|
-e, --email <email> |
Email label for key generation |
-f, --file <path> |
Custom key file path (default: ~/.ssh/id_ed25519) |
-k, --key <pubkey> |
Public key string to add to authorized_keys |
-p, --port <port> |
SSH port to set in sshd_config (default: 22) |
-n, --no-backup |
Skip backup before hardening (dangerous) |
-y, --yes |
Skip confirmation prompts (non-interactive) |
-d, --dry-run |
Preview changes without executing |
-v, --verbose |
Enable debug output |
-h, --help |
Show help message |
| # | Directive | Default | Purpose |
|---|---|---|---|
| 1 | Port |
22 (or custom via --port) |
Always set explicitly |
| 2 | PermitRootLogin |
no |
Block direct root login |
| 3 | PasswordAuthentication |
yes |
Enable password-based auth |
| 4 | KbdInteractiveAuthentication |
yes |
Enable keyboard-interactive auth (OpenSSH 8.2+) |
| 5 | PubkeyAuthentication |
yes |
Enable key-based auth |
| 6 | ChallengeResponseAuthentication |
no |
Disable challenge-response |
| 7 | UsePAM |
yes |
PAM account management |
| 8 | X11Forwarding |
no |
Block X11 tunneling |
| 9 | AllowTcpForwarding |
no |
Block TCP tunneling |
| 10 | PermitTunnel |
no |
Block layer-2/3 tunneling |
| 11 | MaxAuthTries |
3 |
Limit auth attempts |
| 12 | MaxSessions |
5 |
Limit concurrent sessions |
| 13 | LoginGraceTime |
30 |
30s auth window |
| 14 | ClientAliveInterval |
300 |
5-min keepalive |
| 15 | ClientAliveCountMax |
2 |
Disconnect after 2 missed |
| 16 | AllowAgentForwarding |
no |
Block agent forwarding |
| 17 | PermitEmptyPasswords |
no |
No blank passwords |
| 18 | IgnoreRhosts |
yes |
Ignore .rhosts files |
| 19 | HostbasedAuthentication |
no |
Disable host-based auth |
| 20 | PrintMotd |
no |
Use PAM motd instead |
Beyond the directives above, the script also:
- Comments out
AuthenticationMethods— This directive can force specific auth methods and bypassPasswordAuthentication. The script disables it to prevent accidental lockout. - Appends a
Match allblock — GuaranteesPasswordAuthentication yesandKbdInteractiveAuthentication yesapply globally, overriding any per-user or per-groupMatchblocks that might disable password auth.
- Idempotent: Safe to run multiple times — skips existing keys, detects duplicates, only changes differing values
- Effective Config Display:
statususessshd -T(when run as root) to show what sshd actually uses — not just file contents, but the resolved configuration after all includes, Match blocks, and defaults are applied - Fallback Mode: Without root,
statusgreps the config file for uncommented directives and warns that sudo shows the true effective config - Backup & Restore: Automatic timestamped backup before any config mutation, with auto-restore on syntax failure
- Validation: Post-hardening
sshd -tsyntax check - Dry-run mode: Preview every change with
--dry-runbefore applying - Input validation: Port range checks (1–65535), email format, required argument guards
- Root-gated: Hardening refuses to run without root
- Logging: All actions logged to
~/.ssh/ssh-setup.logwith timestamps
- Ed25519 keys (preferred over RSA/ECDSA for security and performance)
- Custom email labels for identification
- Skips existing keys (idempotent)
- Sets correct permissions (600 private, 644 public)
- Linux system (bash 4.4+)
openssh-clientandopenssh-serverpackages- Root access for hardening commands only
# 1. Generate keys
./ssh-setup.sh generate --email "alice@company.com"
# 2. Add your public key to authorized_keys
./ssh-setup.sh add-key
# 3. Check status
./ssh-setup.sh status# 1. Check current configuration
./ssh-setup.sh check
# 2. Preview hardening changes
sudo ./ssh-setup.sh harden --port 22 --dry-run
# 3. Apply hardening
sudo ./ssh-setup.sh harden --port 22 --yes
# 4. Verify
./ssh-setup.sh status# Change SSH port to 2222 and harden
sudo ./ssh-setup.sh harden --port 2222 --yes
# Restart SSH service
sudo systemctl restart sshd
# Verify the port is set
./ssh-setup.sh status# Non-interactive full setup
sudo ./ssh-setup.sh all --yes --email "admin@example.com"If you get "No more authentication methods to try" on mobile SSH clients:
- In your SSH client, explicitly select Password as the authentication method
- Enter your system username and password
- The script sets
PasswordAuthentication yesby default, so password login works
To force password auth from any client:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no user@hostAll actions are logged to ~/.ssh/ssh-setup.log:
tail -f ~/.ssh/ssh-setup.logsudo apt install openssh-server # Debian/Ubuntu
sudo dnf install openssh-server # Fedora/RHEL
sudo yum install openssh-server # CentOS# Hardening requires root
sudo ./ssh-setup.sh hardenThe script creates a backup at /etc/ssh/sshd_config.bak.YYYYMMDDHHMMSS:
# Restore from backup
sudo cp /etc/ssh/sshd_config.bak.* /etc/ssh/sshd_config
sudo systemctl restart sshdRun hardening to add the explicit Port directive:
sudo ./ssh-setup.sh harden --port 22 --yesVerify the effective configuration:
sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication'
# Both should output: yesIf AuthenticationMethods was already set in your config, check it's commented out:
grep -i 'AuthenticationMethods' /etc/ssh/sshd_config
# Lines should be prefixed with #Check the Match all block is present at the end of the config:
tail -5 /etc/ssh/sshd_config
# Should show: Match all / PasswordAuthentication yes / KbdInteractiveAuthentication yesThen restart SSH:
sudo systemctl restart sshd| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | Missing prerequisites |
| 4 | Permission denied |
MIT License