Ubuntu Server Hardening Checklist for Production Use

24 min read 4,678 words
Ubuntu Server Hardening Checklist for Production Web Applications featured image

What a Default Ubuntu Installation Leaves Exposed

A newly installed Ubuntu server arrives with SSH running on port 22, password authentication enabled, and most network ports exposed to the internet. Any server connected online becomes a target within minutes. Automated bots scan continuously for weak SSH credentials, known vulnerabilities in default packages, and misconfigured services that can be exploited. The default configuration tolerates all of this without resistance.

This checklist covers the steps that close those gaps before any web application touches the network. It applies to Ubuntu 22.04 LTS and 24.04 LTS on bare metal, VPS, or cloud instances. Everything uses standard Ubuntu repository packages. The order matters: SSH hardening first, firewall second, then everything else. Skipping SSH hardening before enabling the firewall risks locking yourself out.

Before You Start: Access and Prerequisites

You need a non-root user account with sudo access before touching any hardening steps. If you are already logged in as root, create a deploy user first. You also need console access or IPMI access to the server in case you lock yourself out during configuration. Set aside at least 30 minutes of uninterrupted time. Apply changes in a staging environment first if you have one available.

The assumption throughout this guide is that you are starting with a default Ubuntu installation with no existing hardening applied. If you are working on a server that already has some configuration in place, audit the current state before applying the steps below.

# Check current SSH configuration
sshd -t

# Check which services are running
systemctl list-units --type=service --state=running

# Check UFW firewall status
sudo ufw status

SSH Key-Only Authentication: The Non-Negotiable First Step

SSH is the primary attack surface on any internet-connected server. Default settings are too permissive: password authentication is enabled, root login is allowed, and there is no rate limiting on login attempts. Each of these represents a known exploitation path. Automated brute-force SSH attacks are constant, running from botnets across the globe, and reliably successful against weak credentials.

Password authentication is a liability because it can be brute-forced. A six-character alphanumeric password has roughly two billion possible combinations, which a modern GPU can test in hours. SSH key authentication uses asymmetric cryptography where the private key never leaves your machine and cannot be guessed remotely. Disabling password authentication removes the entire class of remote password-guessing attacks from your threat model.

Generate an ed25519 key pair on your workstation. Ed25519 keys are smaller than RSA keys, faster to authenticate, and considered more secure for most use cases. The NSA's CNSA suite specifies Curve25519 for key exchange and Ed25519 for digital signatures, meaning ed25519 meets government security standards. RSA is acceptable if your tooling requires it, but ed25519 is preferred for new deployments.

# Generate on your local workstation, not the server
ssh-keygen -t ed25519 -C "deploy-key-$(date +%Y)"

When prompted for a passphrase, use one. The key itself is worthless to an attacker without the passphrase, and the passphrase encrypts the private key at rest. A key without a passphrase is readable by anyone who gains access to your workstation filesystem. Store the passphrase in a password manager. The ssh-agent can cache the passphrase so you do not need to type it every time.

# Start the SSH agent
eval "$(ssh-agent -s)"

# Add your key to the agent
ssh-add ~/.ssh/id_ed25519

Copy the public key to the server. The ssh-copy-id command handles creating the ~/.ssh directory and setting permissions correctly. This works only if password authentication is still enabled on the server.

ssh-copy-id -i ~/.ssh/id_ed25519.pub deployuser@203.0.113.50

Test the key login before you disable password authentication. Open a second terminal and attempt to log in with the key. If it works, proceed. If it fails, troubleshoot before continuing. Disabling password authentication with no working key is a lockout scenario that requires console access to resolve.

ssh -i ~/.ssh/id_ed25519 deployuser@203.0.113.50

# Confirm this succeeds before proceeding

Edit the SSH daemon configuration at /etc/ssh/sshd_config. Create a backup first so you can restore if something goes wrong.

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
sudo nano /etc/ssh/sshd_config

Set the following parameters. Find each line in the file, uncomment it if present, or add it if missing.

PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
MaxAuthTries 3
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
PermitEmptyPasswords no

MaxAuthTries 3 means three failed authentication attempts disconnect the session. This stops automated tools that try hundreds of password combinations in one session. Setting it to 3 allows for a mistyped passphrase without locking you out, while stopping bots that hammer the login form. The other settings disable features rarely needed on a server that represent unnecessary attack surface. Agent forwarding and TCP forwarding in particular have been used in post-compromise lateral movement.

Restart the SSH daemon to apply the changes. The restart drops existing connections so new configuration takes effect. Your current session is not affected because you authenticated before the restart.

sudo systemctl restart sshd

Root login is disabled because it bypasses sudo logging and removes the audit trail of who ran what command. Every privileged operation should go through a named user account with sudo. This matters for incident response: when something goes wrong, you need to know which human ran which command, and that is only possible if every command runs under a named account.

With root login disabled, a compromised credential gives the attacker user-level access, not root. That is the difference between a server that needs rebuilding and a server that is immediately fully compromised.

For a deeper look at SSH hardening techniques including key management and additional configuration options, see the guide to securing SSH on Ubuntu.

Changing the SSH Port: Reducing Automated Noise

Port 22 is scanned automatically within minutes of any server connecting to the internet. Automated bots run by spammers and credential stuffers try common usernames and passwords across millions of servers. Moving SSH to a non-standard port reduces this noise so that genuine authentication events are visible in logs rather than buried under automated scanning.

This is not security through obscurity. Security through obscurity means relying on hidden URLs or port-knocking as the primary defence, which is weak. Moving the SSH port simply reduces noise so that targeted attacks become visible in your logs. A port scan of port 22 across the entire IPv4 address space takes less than an hour with modern tools.

Sophisticated attackers scan all ports. Casual bots scan port 22. Moving to a high port stops the casual bots and reduces log volume. It is a minor but worthwhile hardening measure.

Choose a port between 1024 and 65535 that is not already in use. Ports below 1024 require root to bind, but the SSH daemon runs as root so this is not a practical constraint. High-numbered ports are less likely to be scanned in casual sweeps. Do not use a port that another service on your server already uses.

# Check if a port is already in use
sudo ss -tlnp | grep :2222

# If nothing is listening on it, you can use it

Edit /etc/ssh/sshd_config and change the Port line. After saving, update your firewall to allow the new port before restarting SSH. If you restart SSH before the firewall rule is added, you will lock yourself out.

# Add the new SSH port to UFW before restarting SSH
sudo ufw allow 2222/tcp comment "SSH on non-standard port"

# Then restart SSH
sudo systemctl restart sshd

Test the new port before closing your current session. Open a second terminal and connect to the new port. Keep the old session open until you confirm the new connection works, then close the old session and verify you can reconnect on the new port.

ssh -p 2222 -i ~/.ssh/id_ed25519 deployuser@203.0.113.50

Fail2Ban: Stopping Brute-Force Attacks Automatically

Brute-force attacks against SSH are constant on any public server. They run continuously from multiple sources, trying common username and password combinations. A single compromised router or IoT device can generate thousands of login attempts per day. Without Fail2Ban, your logs are full of authentication failures from bots all over the world. With it, repeat offenders are blocked automatically and you can review the ban list to understand who is attacking you.

Fail2Ban monitors authentication logs and automatically bans IP addresses that fail too many login attempts. The ban is implemented via iptables, which blocks all connections from the banned IP for the configured duration. This makes brute-force attacks ineffective because the attacker cannot complete enough attempts to find a valid credential before being banned.

sudo apt update && sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

The default configuration in /etc/fail2ban/jail.conf is reasonable, but package updates will overwrite your changes. Override specific settings in a custom jail file so that updates do not affect your configuration. Create /etc/fail2ban/jail.local for your overrides.

sudo nano /etc/fail2ban/jail.local

Add SSH jail configuration that matches your non-standard port and your tolerance for failed attempts. Setting maxretry to 3 means after three failed attempts from the same IP within findtime (5 minutes), that IP is banned for bantime (600 seconds by default). Adjust these based on how many times you typically mistype your passphrase.

[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 600
findtime = 300
action = iptables-allports
logprotocol = auto
backend = auto

Reload Fail2Ban to apply the new configuration:

sudo fail2ban-client reload

# Check current status
sudo fail2ban-client status sshd

The status output shows how many IPs are currently banned and the list of banned IPs. Check this before assuming your configuration is not working. A ban that has already expired will not appear in the status. Bans are temporary by default. When bantime expires, the IP is unblocked automatically.

# Unban a specific IP if you accidentally locked yourself out
sudo fail2ban-client set sshd unbanip 198.51.100.42

# Check recent bans in the journal
sudo journalctl -u fail2ban -n 50

For a complete walkthrough of Fail2Ban setup including HTTP protection, see the Fail2Ban setup guide.

UFW Firewall: Deny Everything Except What You Explicitly Allow

Uncomplicated Firewall (UFW) is a front-end to netfilter, the Linux kernel packet filtering framework. It is installed by default on Ubuntu but is usually disabled. The default configuration should be deny all incoming connections and allow all outgoing. This is the correct starting point. You explicitly allow only the ports your server needs to function.

sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing

Add the rules for SSH on your non-standard port, plus the web server ports. If you skip the SSH rule before enabling the firewall, you will lock yourself out. This is the most common hardening mistake. People enable UFW and immediately lose SSH access because they forgot to allow the port they were using.

sudo ufw allow 2222/tcp comment "SSH"
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"

If you run a database server that needs external access, add those rules. For most web servers, the only ports that need to be open are SSH, HTTP, and HTTPS. Everything else should be denied by default.

sudo ufw status numbered

# Review the list carefully before proceeding

Enable the firewall. You will see a warning that the command may disrupt existing SSH connections. If your SSH rule is correct, proceed.

sudo ufw enable

# Confirm with 'y' at the prompt

Verify the status shows the firewall as active:

sudo ufw status verbose

If you lock yourself out, cloud providers allow access via their web console or serial console. Physical servers require IPMI or KVM access. Test your firewall rules in staging before applying to production.

Delete a rule if you added the wrong one:

sudo ufw delete 3  # delete rule number 3 from the numbered list

Automatic Security Updates: Closing the Patch Window

Unpatched software is the most common breach vector on Linux servers. The Log4Shell vulnerability was discovered in December 2021 and had active exploitation within days. Servers that had automatic updates configured were patched within hours of the patch being released. Servers that required manual patching remained vulnerable for weeks or months. The window between a security patch being released and it being applied is the window during which known exploits can target your server.

Ubuntu includes unattended-upgrades for this purpose. It checks for security updates daily, downloads them, and applies them automatically. You configure what is upgraded and how notifications are sent.

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades

The dpkg-reconfigure step launches an interactive prompt. Select yes to enable automatic upgrades. After enabling, configure what is upgraded automatically by editing the configuration.

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Extend the default configuration to include updates as well as security updates, and configure notifications so you know when updates run. The Mail parameter sets where notifications are sent. Setting MailOnlyOnError to false means you receive an email every time unattended-upgrades runs, not just when something goes wrong.

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}:${distro_codename}-updates";
};

Unattended-Upgrade::Automatic-Upgrade "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Mail "your_email@yourdomain.com";
Unattended-Upgrade::MailOnlyOnError "false";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::SyslogEnable "true";

Setting Automatic-Reboot to false is intentional for most web servers. A server that reboots unexpectedly during business hours causes an incident. Set this to true only if you have monitoring in place to handle the restart and the business tolerates automatic reboots. For most production servers, a manual reboot during a maintenance window is preferable to an automatic one.

Enable the service and verify it runs without errors:

sudo systemctl enable unattended-upgrades
sudo unattended-upgrades --debug --dry-run

The --dry-run flag runs the process without applying updates. The --debug flag outputs verbose information about what would happen. Review the output to confirm the configuration is correct.

User and Privilege Management

Operating as root via SSH is unnecessary risk. Every command run as root runs with full system privileges and bypasses sudo logging, which removes the audit trail. Use a standard account for day-to-day administration and sudo for privilege escalation. This means every privileged command is logged with the user who ran it.

Create a non-root sudo user:

sudo adduser deployuser
sudo usermod -aG sudo deployuser

# Add your SSH key to the new user
sudo mkdir -p /home/deployuser/.ssh
sudo cp ~/.ssh/authorized_keys /home/deployuser/.ssh/authorized_keys
sudo chown -R deployuser:deployuser /home/deployuser/.ssh
sudo chmod 700 /home/deployuser/.ssh
sudo chmod 600 /home/deployuser/.ssh/authorized_keys

Configure sudo to require a password for privilege escalation. This prevents automated scripts from running privileged commands without an explicit action from a human. Edit /etc/sudoers.d/deployuser:

sudo nano /etc/sudoers.d/deployuser

Add the line that requires a password for sudo access by this user:

deployuser ALL=(ALL) PASSWD: ALL

Set correct permissions on the sudoers drop-in file so that sudoers can read and parse it correctly:

sudo chmod 440 /etc/sudoers.d/deployuser

Audit existing accounts periodically to identify any that should not exist. List all non-system accounts and review whether each is needed:

getent passwd | grep -v "/usr/sbin/nologin" | grep -v "/bin/false"

# Disable an account that should not exist
sudo passwd -l username_to_disable

The -l flag locks the account by prepending !! to the password hash, making the password unusable. The account still exists but cannot be logged into. This is reversible with sudo passwd -u username.

Disable Unused Services: Reducing Attack Surface

A default Ubuntu installation starts services you do not need. Each running service is an additional attack surface: it listens on a network port, it may have vulnerabilities, and it may have misconfigurations that can be exploited. Disable anything that is not required for your server's specific role.

systemctl list-units --type=service --state=running

Review the list. Common services to disable on a dedicated web server include:

  • bluetooth.service: The Bluetooth daemon runs on servers and is never needed for headless deployments. It listens on a local interface and adds kernel attack surface.
  • cups.service: Common Unix Printing System, a print server daemon not needed unless you run a print server.
  • avahi-daemon.service: Zeroconf multicast DNS daemon that creates network noise and potential attack surface. On a server with a static IP and known hostname, it is not needed.
  • rpcbind.service: RPC portmapper, rarely needed on a web server and frequently targeted in scans. The RPC mechanism was largely replaced by HTTP APIs and is considered a security liability on modern servers.
sudo systemctl disable --now bluetooth
sudo systemctl disable --now cups
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now rpcbind

After disabling services, confirm they are not running:

systemctl status bluetooth
systemctl status cups

# Both should show "inactive (dead)"

Kernel Network Hardening with sysctl

Linux kernel parameters control how the network stack handles various types of traffic. The default values are designed for general compatibility, not for security on an internet-facing server. These settings have been validated in production environments across thousands of deployments.

sudo nano /etc/sysctl.d/99-hardening.conf

Add the following settings. Each serves a specific hardening purpose documented in real exploitation scenarios.

# Enable source address validation to prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Disable ICMP redirect acceptance
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Disable source packet routing
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Enable SYN cookies to prevent SYN flood attacks
net.ipv4.tcp_syncookies = 1

# Restrict ICMP broadcasting to prevent amplification attacks
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore bogus ICMP responses
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Disable core dumps for security-sensitive processes
kernel.core_pattern = |/bin/false
fs.suid_dumpable = 0

# Disable kernel debugging interfaces
kernel.kptr_restrict = 2
kernel.yama.scope = 1

Apply the settings without rebooting:

sudo sysctl --system

Verify the settings loaded correctly:

sysctl net.ipv4.tcp_syncookies
sysctl net.ipv4.conf.all.rp_filter

# Both should return 1

SYN cookies allow the server to continue accepting connections during a SYN flood attack by encoding connection state in the SYN-ACK response itself rather than maintaining state on the server. A SYN flood attack that exhausts the connection table on a server without SYN cookies makes the server unresponsive to legitimate traffic. With SYN cookies enabled, the server remains responsive even under attack.

The guide to Ubuntu 22.04 security hardening covers these kernel parameters in more detail along with additional network protections.

Installing and Configuring Auditd

The Linux Audit subsystem records security-relevant events including login attempts, privilege escalation via sudo, and changes to critical system files. This is essential for incident response. When a server is compromised, the audit logs tell you what happened, when it happened, and in some cases who did it. Without auditd, you have system logs but not the detailed security event record that incident response requires.

sudo apt install auditd -y
sudo systemctl enable auditd
sudo systemctl start auditd

Configure watches on critical system files. Each -w directive watches the specified path, -p wa specifies write and attribute change monitoring, and -k assigns a label for searching in the audit logs.

sudo auditctl -w /etc/passwd -p wa -k identity_changes
sudo auditctl -w /etc/shadow -p wa -k identity_changes
sudo auditctl -w /etc/sudoers -p wa -k sudoers_changes
sudo auditctl -w /var/log/ -p wa -k login_activity
sudo auditctl -l

Query the audit log to find events by key name. The aureport command produces a summary of authentication events. The ausearch command finds specific events by key.

sudo aureport --auth --summary
sudo ausearch -k identity_changes
sudo ausearch -k sudoers_changes

Audit events are written to /var/log/audit/audit.log. This file can grow large on busy servers. Configure log rotation for it by adding to /etc/logrotate.d/auditd or by configuring the audit daemon's max_log_file setting in /etc/audit/auditd.conf.

File and Directory Permissions

Enforce least privilege on files and directories that matter most. Incorrect permissions are a common cause of server compromise, particularly for web roots and SSH configuration.

# SSH key directory must be readable only by the owner
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

# Private key must not be group or world readable
chmod 600 ~/.ssh/id_ed25519

# Sudoers directory locked
chmod 755 /etc/sudoers.d

# Web root: adjust path to match your setup
sudo chown -R root:root /var/www/html
sudo chmod -R 755 /var/www/html

Upload directories should never have execute permission. If a PHP file is uploaded to /var/www/html/uploads and the web server can execute it, that upload is a remote code execution vulnerability. Remove execute permissions from upload directories and ensure uploaded files cannot be executed:

# Remove execute from upload directories
find /var/www/html/uploads -type d -exec chmod 755 {} \;
find /var/www/html/uploads -type f -exec chmod 644 {} \;

Check the permissions on SSH private keys. The private key (id_ed25519 or id_rsa) should be 600, readable only by the owner. If it shows 644 or 777, that is a security problem. Any user on the system can read the private key and use it to access your server.

ls -la ~/.ssh/

# The private key should be 600
# The public key can be 644
# The directory should be 700

Installing and Running Lynis for Security Audit

Lynis is an open-source security auditing tool for Unix systems. It runs a comprehensive suite of security tests and produces a hardening report with specific remediation suggestions. Run it on a new server to establish a baseline, then periodically to track hardening progress.

sudo apt install lynis -y
sudo lynis audit system --quick

The --quick flag runs the audit without pausing for interactive prompts. Review the output with attention to warnings and suggestions. Items flagged as medium or high severity are the ones that require attention. The audit results vary based on what services are installed. A web server will have different findings than a database server.

Save the output to a file for comparison when you run the next audit:

sudo lynis audit system --quick > /var/log/lynis-audit-$(date +%Y%m%d).log

Logwatch for Daily System Monitoring

Logwatch summarises system logs and sends a daily email report. Instead of reading raw log files every day, you receive a digest of the most important events. This makes daily review practical without requiring you to grep through log files manually.

sudo apt install logwatch -y
sudo cp /usr/share/logwatch/default.conf/logwatch.conf /etc/logwatch/conf/logwatch.conf

Edit /etc/logwatch/conf/logwatch.conf to set the email address and detail level:

mailto = "your_email@yourdomain.com"
Detail = High
mailer = /usr/sbin/sendmail -t

Logwatch can be run manually or via cron. Add it to the daily cron to receive reports automatically:

# /etc/cron.daily/00logwatch
/usr/sbin/logwatch --output mail --mailto your_email@yourdomain.com --detail high

Common Mistakes That Undermine Hardening

Skipping the test environment is the most consequential mistake. UFW rules that look correct can block required application traffic. sysctl changes can affect kernel networking in ways that break application connectivity. sudoers changes can break deployment scripts that expect passwordless sudo. Test in staging before applying to production.

Setting overly aggressive Fail2Ban bans without testing is a common lockout cause. A maxretry of 1 on a jail that bans for 24 hours locks out legitimate users who mistype their passphrase once. Test any Fail2Ban configuration from an external IP before deploying it across all servers, and ensure you have a way to unban yourself if you get locked out.

Enabling UFW without adding the SSH rule first is the most common lockout cause. Always add the SSH allow rule before enabling the firewall, and test the SSH connection after enabling before closing the session.

Not documenting the changes is a mistake that creates problems later. After applying hardening, document the SSH port, the firewall rules, the unattended-upgrades configuration, and any sysctl changes. This documentation is what you use the next time you set up a server, and what you refer to during incident response. Keep it in a password manager or secure note, not on the server itself.

Skipping Fail2Ban because the port changed is a mistake. The SSH port change reduces automated scanning noise but does not stop targeted attacks. A determined attacker scans all ports. Fail2Ban catches the repeat attempts that port change alone does not address. Key-only authentication plus Fail2Ban plus non-standard port is the complete SSH hardening approach.

After Completing the Checklist

Document everything. Record the non-standard SSH port, the firewall rules in place, the unattended-upgrades configuration, and any sysctl changes made. Keep this documentation somewhere accessible, not on the server itself.

For managing multiple servers, automate the hardening process. Running the same commands manually across three or more servers is error-prone and the configuration drifts. An Ansible playbook or a custom bash script run at first boot applies the same hardening consistently every time.

Set recurring review tasks. Run Lynis quarterly to track hardening progress. Review Fail2Ban logs monthly for unusual patterns. Review unattended-upgrades log after major updates to confirm they applied cleanly. Hardening is not a one-time task. It is an ongoing operational practice.

If you are running web applications on this server, a security review of your application layer can identify vulnerabilities that server hardening alone does not address. The guide to hardening Ubuntu server after install covers additional steps beyond this checklist for servers with specific workloads.

Frequently Asked Questions

Should I use a firewall on a cloud server that has security groups?
Yes. Cloud security groups are network-level controls but they do not protect against threats originating from within the same network segment, container escape scenarios, or misconfigured cloud rules. UFW on the host provides defence in depth and gives you host-level control that cloud security groups may not offer.
How often should I run Lynis audits?
Run an initial audit when setting up the server, then quarterly or after any significant configuration change. Save the output and compare it between runs to track hardening progress and identify new findings.
Is this checklist enough for PCI-DSS or HIPAA compliance?
No. This checklist covers basic server hardening for web application hosting. Compliance frameworks require additional controls including network segmentation, intrusion detection, log retention policies, and regular penetration testing. Use this as a baseline, not a complete security programme.
Can I skip changing the SSH port?
The SSH port change is optional. Fail2Ban and key-only authentication are non-negotiable. The port change reduces automated scanning noise and makes your logs cleaner, but it does not replace proper authentication controls.
What should I monitor every day?
Fail2Ban ban list, authentication success and failure logs, disk usage, and apt upgrade logs are the minimum. Logwatch handles most of the daily review automatically if configured with an email address.
Do I need to restart the server after applying these changes?
Most changes take effect immediately without a restart. The sysctl changes applied with sudo sysctl --system are active right away. A restart is only necessary if you modify kernel modules that are loaded at boot time or if a specific service requires it. Restarting is optional but can help verify that the server boots correctly with all hardening in place.
Can I apply these steps to an existing production server?
Yes, but test in a staging environment first. Changes to SSH configuration, firewall rules, and sysctl settings can cause connectivity issues. Ensure you have console access to the server in case you lock yourself out. Document the current state before making changes so you can restore it if needed.
What is the most important hardening step?
SSH key-only authentication combined with Fail2Ban provides the strongest protection against the most common attack vector. Everything else in this checklist improves defence in depth, but preventing unauthorised SSH access addresses the threat that targets every internet-connected server from the moment it goes online.