--- title: "Initial Server Security Setup on Ubuntu 24.04 and Debian 12" description: "Secure a new Ubuntu 24.04 or Debian 12 server: create a sudo user, use SSH keys only, enable UFW and Fail2Ban, and turn on automatic security updates." category: "Server Setup" language: en source: https://cubepath.com/docs/server-setup/initial-security-configuration-on-ubuntu-debian updated: 2026-09-25 --- # Initial Server Security Setup on Ubuntu 24.04 and Debian 12 A freshly deployed server is exposed to the Internet from its first minute, and automated bots start trying SSH passwords almost immediately. In this tutorial you will apply the baseline every Ubuntu 24.04 or Debian 12 server should have: current packages, a non-root administrator, key-only SSH without root login, a UFW firewall, Fail2Ban, automatic security updates and correct time. Each step includes a check so you know it worked before moving on. ## Prerequisites To follow this guide you need: - A new server running Ubuntu 24.04 LTS or Debian 12, for example a CubePath VPS. - Root access over SSH (password or key) and the server's IP address, shown as `your_server_ip`. - An SSH key pair on your local computer. If you do not have one, create it with `ssh-keygen -t ed25519` as described in our guide on connecting to a server via SSH. - Access to the server's web console (on CubePath, the VNC console in the dashboard) in case an SSH change locks you out. > Important: Keep your current SSH session open until you have confirmed, from a second terminal, that you can still log in after each SSH or firewall change. Differences between Ubuntu and Debian are called out where they matter. ## Step 1 - Updating the system Start by installing all pending updates so the rest of the setup runs on patched packages. Log in as root: ```bash ssh root@your_server_ip ``` Then refresh the package index and upgrade: ```bash apt update apt full-upgrade -y ``` If the kernel or core libraries were updated, a reboot is needed to load them. On Ubuntu, the flag file `/var/run/reboot-required` exists in that case: ```bash ls /var/run/reboot-required 2>/dev/null && reboot ``` If the command prints nothing, no reboot is required. On Debian 12 this file is not always created at this point, so simply reboot if the upgrade installed a new `linux-image` package. After a reboot, wait a minute and log back in as root. ## Step 2 - Creating a non-root sudo user Working as root makes every typo a system-wide risk, and `root` is the first name bots try. Create a regular user for daily work; this guide uses `sammy`, replace it with your own `your_user`: ```bash adduser sammy ``` `adduser` asks for a password and optional details (you can leave those empty). Choose a strong password: you will need it for `sudo` even after SSH switches to keys. Add the user to the `sudo` group, which is allowed to run commands as root on Ubuntu and Debian: ```bash usermod -aG sudo sammy ``` On a minimal Debian 12 install, `sudo` may not be installed. Install it if the next check fails with `command not found`: ```bash apt install -y sudo ``` Verify that the new user can use sudo: ```bash su - sammy -c 'sudo whoami' ``` ```output [sudo] password for sammy: root ``` ## Step 3 - Installing your SSH key for the new user Copy your public key to the new user from your **local computer**: ```bash ssh-copy-id sammy@your_server_ip ``` If your key is already in root's `authorized_keys` (for example because you added it when creating the server), you can copy it on the server instead, with the right ownership and permissions: ```bash rsync --archive --chown=sammy:sammy ~/.ssh /home/sammy ``` Now open a **second** local terminal and log in as the new user: ```bash ssh sammy@your_server_ip ``` You should get a shell without being asked for the server password (only for your key passphrase, if it has one). Run `sudo -v` to confirm sudo works in this session. From here on, work as `sammy`. ## Step 4 - Hardening the SSH server With key login confirmed, turn off password logins and direct root login. Instead of editing the main file, create a drop-in file. OpenSSH keeps the first value it reads for each option and reads `/etc/ssh/sshd_config.d/*.conf` in alphabetical order before the rest of `sshd_config`, so a file named `00-...` overrides defaults such as `50-cloud-init.conf`, which on some images turns password login back on. ```bash sudo nano /etc/ssh/sshd_config.d/00-hardening.conf ``` ```text PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 30 X11Forwarding no AllowUsers sammy ``` What these do: - `PermitRootLogin no`: root cannot log in over SSH at all; you use `sudo` instead. - `PasswordAuthentication no` and `KbdInteractiveAuthentication no`: only keys are accepted. - `MaxAuthTries 3` and `LoginGraceTime 30`: limit attempts per connection and how long an unauthenticated connection may stay open. - `AllowUsers sammy`: only the listed users may log in. Add other administrators here, separated by spaces. Validate the configuration. `sshd -t` prints nothing when the syntax is correct, and `sshd -T` shows the effective values: ```bash sudo sshd -t sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|allowusers)' ``` ```output permitrootlogin no passwordauthentication no allowusers sammy ``` Apply the change: ```bash sudo systemctl restart ssh ``` From your second terminal, confirm that you can still log in as `sammy`, and that root is now rejected: ```bash ssh root@your_server_ip ``` ```output root@203.0.113.10: Permission denied (publickey). ``` ## Step 5 - Enabling the UFW firewall UFW (Uncomplicated Firewall) is a front end for the kernel firewall. It is installed by default on Ubuntu; on Debian 12, install it first: ```bash sudo apt install -y ufw ``` Set a default policy that blocks incoming traffic and allows outgoing traffic, then allow SSH **before** enabling the firewall. The `limit` rule allows SSH but temporarily blocks an address that opens 6 or more connections within 30 seconds: ```bash sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw limit OpenSSH ``` If you run a web server, open its ports now too: ```bash sudo ufw allow 80/tcp sudo ufw allow 443/tcp ``` Enable the firewall and answer `y`: ```bash sudo ufw enable ``` Check the result: ```bash sudo ufw status verbose ``` ```output Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), deny (routed) New profiles: skip To Action From -- ------ ---- 22/tcp (OpenSSH) LIMIT IN Anywhere 80/tcp ALLOW IN Anywhere 443/tcp ALLOW IN Anywhere 22/tcp (OpenSSH (v6)) LIMIT IN Anywhere (v6) 80/tcp (v6) ALLOW IN Anywhere (v6) 443/tcp (v6) ALLOW IN Anywhere (v6) ``` Open a new SSH session to confirm you are not locked out. > Note: If you later change the SSH port, allow the new port in UFW before restarting SSH. ## Step 6 - Installing Fail2Ban Even with passwords disabled, bots keep hammering port 22 and fill your logs. Fail2Ban watches authentication failures and bans offending IP addresses for a while. ```bash sudo apt install -y fail2ban ``` Never edit `jail.conf` directly, because package upgrades overwrite it. Put your settings in `jail.local`: ```bash sudo nano /etc/fail2ban/jail.local ``` ```ini [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 banaction = ufw [sshd] enabled = true backend = systemd ``` `banaction = ufw` makes bans appear as UFW rules, and `backend = systemd` reads SSH events from the journal, which works on Ubuntu 24.04 and on Debian 12 (where `/var/log/auth.log` does not exist by default). If you manage the server from a fixed IP address, add `ignoreip = 127.0.0.1/8 ::1 your_ip` under `[DEFAULT]` so you can never ban yourself. Enable the service and check the SSH jail: ```bash sudo systemctl enable --now fail2ban sudo systemctl restart fail2ban sudo fail2ban-client status sshd ``` ```output Status for the jail: sshd |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd `- Actions |- Currently banned: 0 |- Total banned: 0 `- Banned IP list: ``` Within a few hours on a public IP the failed and banned counters will start to rise. To unban an address manually, run `sudo fail2ban-client set sshd unbanip 198.51.100.7`. ## Step 7 - Turning on automatic security updates `unattended-upgrades` installs security updates daily without waiting for you. It is installed and enabled by default on Ubuntu 24.04; on Debian 12, install it: ```bash sudo apt install -y unattended-upgrades ``` Make sure the daily run is enabled. Choose **Yes** in the dialog: ```bash sudo dpkg-reconfigure --priority=low unattended-upgrades ``` This writes `/etc/apt/apt.conf.d/20auto-upgrades`. Confirm its content: ```bash cat /etc/apt/apt.conf.d/20auto-upgrades ``` ```output APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; ``` By default only packages from the security origin are installed automatically, and the server is never rebooted. If you want a scheduled reboot when a kernel update requires it, create a small override file: ```bash sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-local ``` ```text Unattended-Upgrade::Automatic-Reboot "true"; Unattended-Upgrade::Automatic-Reboot-Time "04:00"; ``` Run a dry run to check that the configuration is valid and see what would be installed: ```bash sudo unattended-upgrade --dry-run --debug ``` The output lists the allowed origins and the packages it would upgrade, and ends without errors. Real runs are logged in `/var/log/unattended-upgrades/unattended-upgrades.log`. ## Step 8 - Setting the timezone and time synchronization Accurate time matters for logs, TLS certificates and Fail2Ban's time windows. Check the current state: ```bash timedatectl ``` ```output Local time: Thu 2026-09-24 10:15:02 UTC Universal time: Thu 2026-09-24 10:15:02 UTC RTC time: Thu 2026-09-24 10:15:02 Time zone: Etc/UTC (UTC, +0000) System clock synchronized: yes NTP service: active RTC in local TZ: no ``` Keeping servers on UTC makes logs from different machines easy to compare. If you prefer a local zone, list them with `timedatectl list-timezones` and set one: ```bash sudo timedatectl set-timezone Europe/Madrid ``` If `NTP service` shows `inactive` (common on minimal Debian installs), install and enable `systemd-timesyncd`: ```bash sudo apt install -y systemd-timesyncd sudo timedatectl set-ntp true ``` Run `timedatectl` again and confirm `System clock synchronized: yes`. ## Step 9 - Reviewing listening services Every service that listens on a public address is attack surface. List what is listening on TCP and UDP: ```bash sudo ss -tulpn ``` ```output Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process udp UNCONN 0 0 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=512,fd=16)) udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=512,fd=14)) tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1021,fd=3),("systemd",pid=1,fd=86)) tcp LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=1021,fd=3),("systemd",pid=1,fd=86)) ``` Addresses on `127.0.0.x` are only reachable from the server itself. On a new server you should only see SSH on a public address. If you find something you do not need, stop and disable it, for example: ```bash sudo systemctl disable --now service_name ``` Only disable services you recognize. The firewall from Step 5 already blocks anything you did not explicitly allow, so this step is about keeping the server lean rather than a replacement for UFW. ## Verification Run through this checklist from a fresh terminal on your local computer and on the server: | Check | Command | Expected | |-------|---------|----------| | Key login as your user | `ssh sammy@your_server_ip` | Shell, no server password prompt | | Root login blocked | `ssh root@your_server_ip` | `Permission denied (publickey)` | | Firewall active | `sudo ufw status` | `Status: active` with SSH allowed | | Fail2Ban running | `sudo fail2ban-client status sshd` | Jail listed | | Automatic updates | `systemctl status apt-daily-upgrade.timer` | `active (waiting)` | | Time synced | `timedatectl` | `System clock synchronized: yes` | ## Troubleshooting **Locked out after an SSH change**: open the web console of the server, log in as your user, and review `/etc/ssh/sshd_config.d/00-hardening.conf`. The most common mistake is an `AllowUsers` line that does not include your user. Fix it, run `sudo sshd -t`, then `sudo systemctl restart ssh`. **Locked out after enabling UFW**: from the web console run `sudo ufw allow OpenSSH` (or your custom port) and try again. `sudo ufw disable` is a temporary way back in, not a fix. **Fail2Ban does not start**: run `sudo fail2ban-client -t` to test the configuration and `sudo journalctl -u fail2ban -n 50` to see the error. A typo in a section name such as `[sshd]` is the usual cause. **You banned yourself**: from the web console run `sudo fail2ban-client set sshd unbanip your_ip`, then add your address to `ignoreip`. ## Conclusion Your Ubuntu or Debian server now has a patched base system, a non-root administrator, key-only SSH, a default-deny firewall, brute-force protection and automatic security updates. From here you can change the SSH port to cut log noise, set up regular backups of your data, and install the services the server is meant to run, opening only their ports in UFW.