OpenSSH is the standard tool for secure remote access to Linux servers. It encrypts all traffic between client and server, replacing older insecure protocols like Telnet and rlogin. Every production Linux server needs a properly configured SSH server – it is the primary way you manage systems remotely.
This guide covers a full OpenSSH server setup on Ubuntu 24.04 LTS and Debian 13, from installation through advanced hardening. We walk through key-based authentication, firewall rules, fail2ban, SSH tunneling, multi-factor authentication, and troubleshooting. The official OpenSSH documentation is a good reference for additional configuration options.
Prerequisites
Before starting, make sure you have:
- A server running Ubuntu 24.04 LTS or Debian 13 with root or sudo access
- A static IP address or hostname for the server
- Network connectivity between client and server on TCP port 22 (or your chosen SSH port)
- A local machine (Linux, macOS, or Windows with OpenSSH client) for connecting
Step 1: Install OpenSSH Server on Ubuntu 24.04 / Debian 13
OpenSSH server is available in the default repositories for both Ubuntu and Debian. Update the package index and install it.
sudo apt update
sudo apt install -y openssh-server
After installation, verify that the SSH package is installed correctly.
dpkg -l | grep openssh-server
The output confirms the package version and installation status:
ii openssh-server 1:9.6p1-3ubuntu13.5 amd64 secure shell (SSH) server, for secure access from remote machines
Step 2: Start and Enable the SSH Service
Enable the SSH daemon so it starts automatically on boot, then start it immediately.
sudo systemctl enable --now ssh
Verify the service is running with no errors:
sudo systemctl status ssh
You should see the service active and running:
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-03-22 10:15:32 UTC; 5s ago
Main PID: 1234 (sshd)
Tasks: 1 (limit: 4614)
Memory: 1.8M
CPU: 25ms
CGroup: /system.slice/ssh.service
└─1234 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
Confirm that SSH is listening on port 22:
ss -tlnp | grep sshd
The output should show sshd listening on all interfaces:
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=1234,fd=4))
Step 3: Configure SSH Server (sshd_config)
The main SSH server configuration file is /etc/ssh/sshd_config. Before making changes, create a backup of the original.
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
Open the configuration file for editing:
sudo vi /etc/ssh/sshd_config
Update the following directives. Uncomment any line that starts with # and set the values as shown:
# Change the default port (optional but recommended)
Port 2222
# Disable root login - use a regular user with sudo instead
PermitRootLogin no
# Disable password authentication after setting up SSH keys (Step 5)
# Keep this as 'yes' until you confirm key-based login works
PasswordAuthentication yes
# Restrict SSH access to specific users
AllowUsers deployer admin
# Disable empty passwords
PermitEmptyPasswords no
# Set maximum authentication attempts
MaxAuthTries 3
# Set login grace time (seconds to authenticate before disconnecting)
LoginGraceTime 60
# Disable X11 forwarding unless specifically needed
X11Forwarding no
# Use Protocol 2 only (default on modern OpenSSH but explicit is better)
Protocol 2
The AllowUsers directive is one of the most effective restrictions – only listed usernames can log in via SSH. Replace deployer and admin with your actual usernames.
Validate the configuration syntax before restarting:
sudo sshd -t
If the command returns no output, the configuration is valid. Restart SSH to apply changes:
sudo systemctl restart ssh
Important: If you changed the port, keep your current SSH session open and test connecting on the new port from a second terminal before closing the original session. This prevents lockouts.
ssh -p 2222 deployer@your-server-ip
Step 4: Set Up SSH Key-Based Authentication
Key-based authentication is more secure than passwords. It uses a cryptographic key pair – a private key on your local machine and a public key on the server. Generate a key pair on your local machine (not the server).
ssh-keygen -t ed25519 -C "[email protected]"
The Ed25519 algorithm is the recommended choice – it is faster and more secure than RSA for new keys. You will be prompted for a file location and passphrase:
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pub
Always set a passphrase on your private key. It adds a second layer of protection if someone gains access to your key file.
Copy the public key to the server using ssh-copy-id:
ssh-copy-id -p 2222 deployer@your-server-ip
This appends your public key to ~/.ssh/authorized_keys on the server. If ssh-copy-id is not available (some minimal systems), copy the key manually:
cat ~/.ssh/id_ed25519.pub | ssh -p 2222 deployer@your-server-ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Test that key-based authentication works by connecting without being prompted for a password (you may be prompted for your key passphrase):
ssh -p 2222 deployer@your-server-ip
Step 5: Disable Password Authentication
Once you have confirmed that key-based login works, disable password authentication entirely. This eliminates brute-force password attacks.
sudo vi /etc/ssh/sshd_config
Set the following directives:
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM no
Restart the SSH service:
sudo systemctl restart ssh
Verify that password login is rejected. From your local machine, try connecting with password authentication forced:
ssh -p 2222 -o PubkeyAuthentication=no deployer@your-server-ip
The connection should be denied with “Permission denied (publickey)” – confirming that only key-based login is allowed.
Step 6: Configure UFW Firewall for SSH
A firewall is essential on any server exposed to the internet. UFW (Uncomplicated Firewall) is the default firewall management tool on Ubuntu and Debian. If you are using the default SSH port, allow it through the firewall. For a deeper look at UFW rules and usage, check out UFW firewall usage commands with examples.
Install UFW if it is not already present:
sudo apt install -y ufw
If you changed the SSH port to 2222 in Step 3, allow that port instead of the default:
sudo ufw allow 2222/tcp comment "SSH"
If you kept the default port 22:
sudo ufw allow OpenSSH
Enable the firewall and check its status:
sudo ufw enable
sudo ufw status verbose
The output should show your SSH port allowed:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
2222/tcp ALLOW IN Anywhere # SSH
2222/tcp (v6) ALLOW IN Anywhere (v6) # SSH
For tighter security, restrict SSH access to specific IP addresses or subnets only:
sudo ufw allow from 10.0.1.0/24 to any port 2222 proto tcp comment "SSH from office network"
Step 7: SSH Hardening with Fail2ban
Fail2ban monitors log files for failed authentication attempts and temporarily bans offending IP addresses. This is critical for any internet-facing SSH server, even with key-based authentication enabled.
Install fail2ban:
sudo apt install -y fail2ban
Create a local configuration file. Never edit the main /etc/fail2ban/jail.conf directly – it gets overwritten on upgrades.
sudo vi /etc/fail2ban/jail.local
Add the following SSH jail configuration:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
ignoreip = 127.0.0.1/8 10.0.1.0/24
This configuration bans an IP for 1 hour (3600 seconds) after 3 failed attempts within 10 minutes (600 seconds). The ignoreip directive prevents your own network from being locked out. Adjust the port number to match your SSH configuration.
Enable and start fail2ban:
sudo systemctl enable --now fail2ban
Check the SSH jail status to confirm it is active:
sudo fail2ban-client status sshd
The output shows the jail is running with the filter active:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
Beyond fail2ban, these sshd_config directives further harden your SSH server. Make sure they are set in /etc/ssh/sshd_config:
# Disconnect idle sessions after 5 minutes
ClientAliveInterval 300
ClientAliveCountMax 0
# Limit concurrent unauthenticated connections
MaxStartups 10:30:60
# Disable agent forwarding unless needed
AllowAgentForwarding no
# Disable TCP forwarding unless needed
AllowTcpForwarding no
# Log more detail for auditing
LogLevel VERBOSE
Restart SSH after applying these changes:
sudo systemctl restart ssh
Step 8: SSH Tunneling and Port Forwarding
SSH tunneling lets you forward traffic through an encrypted connection. This is useful for accessing services on remote networks or securing traffic for applications that do not support encryption natively. For more SSH tunnel techniques, see how to create SSH tunnels on Linux.
Local Port Forwarding
Forward a port on your local machine to a service on the remote server or a host accessible from the server. This example forwards local port 8080 to a web application running on port 3000 on the remote server:
ssh -L 8080:localhost:3000 -p 2222 deployer@your-server-ip
Access the application by opening http://localhost:8080 in your browser. The traffic is encrypted through the SSH tunnel.
Remote Port Forwarding
Make a local service accessible from the remote server. This forwards port 9090 on the server to port 9090 on your local machine:
ssh -R 9090:localhost:9090 -p 2222 deployer@your-server-ip
Dynamic Port Forwarding (SOCKS Proxy)
Create a SOCKS proxy through the SSH connection. This routes all traffic from applications configured to use the proxy through the SSH tunnel:
ssh -D 1080 -p 2222 -N deployer@your-server-ip
The -N flag tells SSH not to execute a remote command – just establish the tunnel. Configure your browser or application to use localhost:1080 as a SOCKS5 proxy.
Step 9: Multi-Factor Authentication with Google Authenticator
Adding a time-based one-time password (TOTP) to SSH provides an extra layer of security beyond keys. Even if an attacker obtains your private key, they still need the TOTP code. For CentOS/RHEL-specific setup, check out how to configure SSH two-factor authentication on CentOS/RHEL.
Install the Google Authenticator PAM module:
sudo apt install -y libpam-google-authenticator
Run the setup as the user who will log in (not root):
google-authenticator
Answer the prompts as follows:
- Time-based tokens? – Yes
- Update .google_authenticator file? – Yes
- Disallow multiple uses of the same token? – Yes
- Increase time window? – No (keep the default 30-second window)
- Enable rate limiting? – Yes
The command displays a QR code and emergency backup codes. Scan the QR code with your authenticator app (Google Authenticator, Authy, or any TOTP app). Save the emergency codes in a secure location.
Configure PAM to require the TOTP token. Open the PAM SSH configuration:
sudo vi /etc/pam.d/sshd
Add this line at the end of the file:
auth required pam_google_authenticator.so
Update /etc/ssh/sshd_config to enable challenge-response authentication:
sudo vi /etc/ssh/sshd_config
Set these directives:
KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
The AuthenticationMethods directive requires both a valid SSH key and a TOTP code for login. Restart SSH:
sudo systemctl restart ssh
Test from a new terminal session. You should be prompted for your verification code after the key is accepted.
Step 10: Troubleshoot Common SSH Issues
When SSH connections fail, systematic debugging saves time. Here are the most common issues and their fixes. If you ever need to temporarily bypass host key verification for testing, see how to disable SSH host key checking.
Connection Refused
This means sshd is not running or is listening on a different port. Check the service and port:
sudo systemctl status ssh
ss -tlnp | grep sshd
Permission Denied (publickey)
This usually means the key is not in authorized_keys or file permissions are wrong. Fix permissions on the server:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chown -R $USER:$USER ~/.ssh
Verbose Connection Debugging
Use the -vvv flag for maximum debug output on the client side:
ssh -vvv -p 2222 deployer@your-server-ip
On the server side, check the authentication log for errors:
sudo tail -50 /var/log/auth.log
Host Key Changed Warning
If you reinstalled the server or changed its IP, you will see a “WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED” message. Remove the old key from your known hosts file:
ssh-keygen -R your-server-ip
Slow SSH Login
Slow login is usually caused by DNS reverse lookups. Disable it in /etc/ssh/sshd_config:
UseDNS no
Restart SSH after the change. You can also mount remote directories over SSH using SSHFS on Linux for file management.
SSH Server Configuration Reference Table
This table summarizes the most commonly used sshd_config directives and their recommended values for a hardened SSH server. Refer to the Ubuntu OpenSSH server documentation for the full list of available options.
| Directive | Recommended Value | Description |
|---|---|---|
| Port | 2222 (non-default) | Change from 22 to reduce automated scan hits |
| PermitRootLogin | no | Disable direct root login – use sudo instead |
| PasswordAuthentication | no | Disable after setting up SSH keys |
| PubkeyAuthentication | yes | Enable key-based authentication |
| AllowUsers | user1 user2 | Whitelist specific users for SSH access |
| MaxAuthTries | 3 | Limit failed authentication attempts per connection |
| LoginGraceTime | 60 | Seconds to authenticate before disconnect |
| ClientAliveInterval | 300 | Seconds between keepalive checks for idle sessions |
| ClientAliveCountMax | 0 | Disconnect after this many missed keepalives |
| MaxStartups | 10:30:60 | Rate limit unauthenticated connections |
| X11Forwarding | no | Disable unless graphical forwarding is needed |
| AllowAgentForwarding | no | Disable unless needed for jump hosts |
| AllowTcpForwarding | no | Disable unless tunneling is required |
| PermitEmptyPasswords | no | Never allow empty password authentication |
| UseDNS | no | Disable reverse DNS lookup to speed up login |
| LogLevel | VERBOSE | Detailed logging for security auditing |
Conclusion
You now have a fully configured and hardened SSH server on Ubuntu 24.04 or Debian 13. The setup covers key-based authentication, firewall rules, fail2ban intrusion prevention, SSH tunneling, and optional multi-factor authentication. These layers together make brute-force and unauthorized access significantly harder.
For production environments, also consider setting up centralized log monitoring for SSH authentication events, rotating host keys periodically, and using SSH certificates instead of individual authorized_keys files for large-scale deployments.
This was very helpful, thanks 😀
Really helpful. Thank you!
Thanks for the comment Abiola. Welcome always…
Always welcome @ Kris
Who da man??? You da man!!!
very helpful n easy than other