SECURITY
How to secure a Linux server: the first hour checklist
By Anupa Dinuranga, FounderUpdated 9 min read
Before you start: update and keep a session open
These steps are written for a fresh Ubuntu 24.04 LTS server where your provider gave you either a root password or a root SSH key. Most of it applies to Debian too. Do it before you deploy anything: a server with a public IP gets automated login attempts very soon after it boots.
Log in as root and bring the system up to date first:
apt update && apt full-upgrade -y
# If this file exists, a reboot is needed (usually a new kernel)
[ -f /var/run/reboot-required ] && rebootSeveral steps below change how you log in. Keep one SSH session open the whole time and test every change from a second terminal. If the new login fails, you still have the first session to undo it. Close the original session only after the new login works.
Step 1: Create a non-root sudo user
Working as root means every typo runs with full power, and every attacker knows the username. Create your own user and give it sudo rights. Replace deploy with whatever name you like.
adduser deploy
usermod -aG sudo deployadduser asks for a password. Set a strong one: you won't use it to log in over SSH, but sudo will ask for it.
Step 2: Log in with an SSH key
On your own computer (not the server), create an ed25519 key if you don't already have one. Give it a passphrase so a stolen laptop doesn't mean a stolen server.
ssh-keygen -t ed25519 -C "you@your-laptop"Copy the public key to the new user. On Linux or macOS:
ssh-copy-id deploy@SERVER_IPWindows doesn't ship ssh-copy-id. In PowerShell, this does the same job:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh deploy@SERVER_IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"If your provider only allows key login for root, copy root's authorized keys to the new user from your existing root session instead:
rsync --archive --chown=deploy:deploy ~/.ssh /home/deployNow, in a second terminal, check that ssh deploy@SERVER_IP logs you in without a password prompt and that sudo whoami prints root. Don't continue until both work.
Step 3: Turn off root and password login
On Ubuntu 24.04, /etc/ssh/sshd_config starts with Include /etc/ssh/sshd_config.d/*.conf. Put your changes in a drop-in file there rather than editing the main file, so package updates don't fight with you. For most settings sshd uses the first value it reads, and files are read in name order, so a name starting with 00- wins over files like 50-cloud-init.conf that some providers add with PasswordAuthentication yes.
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yesCheck the syntax, confirm the settings sshd will actually use, then restart it:
sudo sshd -t
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication)'
sudo systemctl restart sshOn Ubuntu the service is called ssh, not sshd. Restarting it doesn't drop sessions that are already open. Now test from a second terminal:
ssh deploy@SERVER_IPshould still log in with your key.ssh root@SERVER_IPshould fail withPermission denied (publickey).ssh -o PubkeyAuthentication=no deploy@SERVER_IPshould fail without ever asking for a password.
Only when all three behave as expected should you close your original root session.
Step 4: Close every port you don't need
Ubuntu ships with ufw, a simple front end to the kernel firewall. Allow SSH before you enable it, or you will lock yourself out.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
# Only if this server serves websites:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseThen check what is actually listening on the network:
sudo ss -tulpnAnything bound to 0.0.0.0 or [::] listens on every interface. Databases such as MySQL, PostgreSQL and Redis should listen on 127.0.0.1 unless another server genuinely needs to reach them, and then only that server's IP should be allowed through. Your Node.js or Python app should sit behind Nginx on a local port, not face the internet directly.
If your provider also offers a network firewall in its control panel, use it as a second layer with the same rules. It blocks traffic before it reaches the server, and it still protects you if a package or a script ever changes the rules on the machine itself. Keep both lists in sync, and write down why each open port is open, so the next person (or you, in six months) knows what is safe to close.
Docker publishes container ports by writing its own firewall rules, which bypass ufw. A container started with -p 3000:3000 is reachable from the internet even if ufw says port 3000 is closed. Publish to localhost instead (-p 127.0.0.1:3000:3000) and let Nginx proxy to it. Our guide to Docker in production covers this in more detail.
Step 5: Automatic security updates and fail2ban
Unattended upgrades
Ubuntu Server normally ships with unattended-upgrades enabled for security updates, but images vary between providers, so check rather than assume:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
cat /etc/apt/apt.conf.d/20auto-upgrades
sudo unattended-upgrade --dry-run --debug20auto-upgrades should contain APT::Periodic::Update-Package-Lists "1"; and APT::Periodic::Unattended-Upgrade "1";. Security updates will then install daily. Kernel updates still need a reboot. You can either reboot by hand when /var/run/reboot-required appears, or set Unattended-Upgrade::Automatic-Reboot "true"; and an Automatic-Reboot-Time in /etc/apt/apt.conf.d/50unattended-upgrades if a short nightly outage is acceptable.
fail2ban
fail2ban watches logs and temporarily bans IPs that keep failing to log in. With key-only SSH, brute force is already unlikely to succeed, but fail2ban cuts the noise and slows down scanners.
sudo apt install fail2banNever edit jail.conf; put your settings in jail.local so updates don't overwrite them:
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1hsudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshdStep 6: Backups you have restored, and a look at the logs
Backups
A backup stored on the same server disappears with the server. Send backups somewhere else: another server, object storage or a backup service. Dump databases with mysqldump or pg_dump first, because copying a running database's files is not a reliable backup. Restic, available from Ubuntu's repositories, handles encrypted, deduplicated backups to an SFTP server or object storage:
apt install restic
export RESTIC_REPOSITORY="sftp:backup@BACKUP_HOST:/srv/restic/web1"
export RESTIC_PASSWORD_FILE=/root/.restic-password # chmod 600, and keep a copy off the server
restic init # once
restic backup /etc /var/www /home/deploy/db-dumps
restic snapshots
# The part people skip: prove you can get the data back
restic restore latest --target /tmp/restore-testRun the backup from a systemd timer or cron, and put a restore test in your calendar. If you have never restored from a backup, you don't know that you have one.
Monitoring and logs
Set up an external uptime check that alerts your phone, then get into the habit of a quick look:
df -h # disk space
free -h # memory
sudo journalctl -p err -b # errors since the last boot
sudo journalctl -u ssh --since today # SSH logins and failures
sudo fail2ban-client status sshd # who is being banned
systemctl --failed # services that have crashedFull disks, crashed services and failed certificate renewals are what usually take a small server down, and all three show up here before your visitors notice. If you plan to put the server behind a CDN, our guide to using Cloudflare with your own server explains how to lock the web ports to Cloudflare as well.
Where NevaraCloud fits
This checklist is the baseline for any server you run yourself, and keeping it up is ongoing work. If you'd rather not own that work, our managed hosting takes care of patching, firewalls and SSL for you. We don't sell VPSs; if you aren't sure which way to go, read managed hosting vs a VPS.
If you already run your own server and want a second pair of eyes on it, or something has broken, we can harden or fix an existing setup, quoted after we look at it.