GuidesStart hereSecure a new Debian server

Secure a new Debian server

The first steps on a new Debian 13 server, from logging in with an SSH key and turning off password logins to automatic security updates and a ufw firewall that opens only the ports you use.

Tested on Debian 13 (trixie) on a Melonslab server Step 3 of 4 for beginners Updated September 26, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

A new server is found within hours. Our test server logged its first password guess less than three hours after it came online, and 1,636 in its first day, from 11 addresses. Most of them tried to log in as root.

These steps take about 15 minutes, and every other guide here builds on a server set up like this:

  1. you log in with a key instead of a password,
  2. the server stops accepting passwords over SSH, so guessing one gets nobody in,
  3. security updates install themselves every day, and
  4. a firewall, ufw, keeps every port closed that you have not opened.

Every step below was run on a fresh Melonslab server with Debian 13, and checked again after a reboot.

Before you start

You need a server with Debian 13 and its root password. If you have not logged in to it yet, the connect guide shows how. The examples use 203.0.113.10 for your server's IPv4 address. Replace it with your own throughout.

If something goes wrong, the console gets you back in: in the client area, open your server and choose Open console on the Server tab. It shows the server's screen as if you were standing at it, and works without SSH or the network.

1. Log in with a key

On your own computer, in a terminal (on Windows, in PowerShell), create a key:

ssh-keygen -t ed25519

Press Enter to keep the suggested file, and choose a passphrase: it protects the key if someone copies it from your computer. This makes two files: id_ed25519 is the private key, which never leaves your computer, and id_ed25519.pub is the public key, which goes on the server.

Copy the public key to the server. This is the last time you type the server's root password:

ssh-copy-id root@203.0.113.10

Windows has no ssh-copy-id. In PowerShell, run this instead:

type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@203.0.113.10 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Now log in:

ssh root@203.0.113.10

If it asks for the key's passphrase and not the server's password, the key works.

2. Turn off password logins

Keep this session open until the end of the step. If a change goes wrong, you can still undo it here.

Create /etc/ssh/sshd_config.d/10-keys-only.conf:

# Log in with keys only. Named 10- so it is read before 50-cloud-init.conf,
# whose PasswordAuthentication yes would otherwise win.
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password

The folder already has a file, 50-cloud-init.conf, that turns password logins on. SSH reads the files in name order and keeps the first value it finds for each setting, so a name starting with 10 comes first and wins. PermitRootLogin prohibit-password still lets root in with a key.

Check the configuration and apply it:

sshd -t && systemctl reload ssh

sshd -t only prints something if there is a mistake, and SSH is only reloaded when there is none. To see the settings SSH now uses:

sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin) '
permitrootlogin without-password
passwordauthentication no
kbdinteractiveauthentication no

without-password is an older name for prohibit-password. Now open a second terminal and log in again with ssh root@203.0.113.10. When that works, you can close the first session.

To check that passwords are refused, ask SSH not to use your key:

ssh -o PubkeyAuthentication=no root@203.0.113.10

It answers Permission denied (publickey). without asking for a password.

You do not need Fail2ban for SSH now. Guesses can no longer succeed, they only fill the log.

3. Install security updates automatically

apt update
apt install -y unattended-upgrades curl

This also installs curl, which many of our guides use to download files and test web servers: a fresh server does not have it. Installing unattended-upgrades does not switch it on. Create /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

The server now fetches the package lists and installs updates from Debian and Debian's security team once a day, in the morning. To see what it would do, without installing anything:

unattended-upgrade --dry-run --debug 2>&1 | grep -E 'Allowed origins|No packages'

The first line lists label=Debian-Security among the sources it installs from. systemctl list-timers apt-daily-upgrade.timer shows when it runs next.

A new kernel only takes effect after a restart. To let the server restart by itself at 04:00 when an update needs it, create /etc/apt/apt.conf.d/52unattended-upgrades-local:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Apps set up as in our other guides start again by themselves after a restart. If you would rather restart yourself, leave this file out: the file /run/reboot-required appears when an update is waiting for a restart.

4. Open only the ports you use

Debian starts without a firewall, so every program that listens on a port can be reached from the internet. ufw, short for "uncomplicated firewall", blocks everything that comes in except what you allow, for both IPv4 and IPv6.

Install it, and allow SSH before you switch it on, or you lock yourself out:

apt install -y ufw
ufw allow 22/tcp

If the server will serve websites, allow HTTP and HTTPS too. HTTPS uses UDP as well as TCP for HTTP/3:

ufw allow 80,443/tcp
ufw allow 443/udp

Then switch it on. It warns that it may disrupt SSH connections: answer y. The rule for port 22 keeps yours open.

ufw enable
ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80,443/tcp                 ALLOW IN    Anywhere
443/udp                    ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
80,443/tcp (v6)            ALLOW IN    Anywhere (v6)
443/udp (v6)               ALLOW IN    Anywhere (v6)

ufw starts again after a reboot with the same rules. To remove one, give the same rule after ufw delete, for example ufw delete allow 443/udp.

GuideRun
WireGuard VPNufw allow 51820/udp and ufw route allow in on wg0 out on eth0
Unbound DNS, for VPN devicesufw allow in on wg0
Matrix callsufw allow 7881/tcp and ufw allow 7882/udp, and for coturn ufw allow 3478 and ufw allow 49152:65535/udp
Stalwart mail serverufw allow 25,465,993,995/tcp
Forgejo Git serverufw allow 2222/tcp
Headscaleufw allow 3478/udp
Zabbixufw allow from 198.51.100.30 to any port 10051 proto tcp, once for each server you watch
FreeRADIUSufw allow from 198.51.100.20 to any port 1812:1813 proto udp, with your office's address
Postfix mail serverufw allow 25,80,587,993/tcp, with 80 for certificate renewal
Minecraft serverufw allow 25565/tcp
Asteriskufw allow 5061/tcp and ufw allow 10000:20000/udp
Pelican and Pterodactylufw allow 8080,2022/tcp, for Wings
Jitsi Meetufw allow 10000/udp, ufw allow 3478/udp and ufw allow 5349/tcp
AdGuard Homeufw allow 853/tcp and ufw allow in on wg0

The WireGuard rules are two: one lets devices connect, and the route rule lets their traffic pass through the server to the internet, which ufw blocks by default.

Rootless Podman containers, as in the Podman guide, follow the same rules as any other program: a port they publish to the internet stays closed until you allow it. Ports published only on 127.0.0.1, as our app guides do behind Caddy, are never reachable from outside.

5. Check what is listening

ss -tulpn

This lists every program that listens on a port. A local address of 0.0.0.0, [::] or * means every address, which ufw keeps closed unless you allowed the port. 127.0.0.1 and [::1] can only be reached from the server itself. If you find something you do not use, remove it rather than leaving it to the firewall.

Troubleshooting

You are locked out. Choose Open console in the client area and log in as root with the password. From there, ufw disable switches off the firewall, and deleting /etc/ssh/sshd_config.d/10-keys-only.conf and running systemctl reload ssh turns password logins back on. If you have lost the root password too, the Root password section on the same tab sets a new one.

Permission denied (publickey) from your own computer. The server did not accept your key. Check that you log in as root from the computer where you made the key in step 1. If you use another computer, add its public key to /root/.ssh/authorized_keys from a session that works, or from the console.

Devices on the VPN lose the internet after ufw enable. The route rule for WireGuard is missing. If they lose DNS but not the internet, the rule for Unbound is. Both are in the table above.

A website or app stops answering. Its port is not allowed. ss -tulpn shows which port it listens on.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides