Back to Home

Projects

22 Jun 2026

·

8 min read

·

DevOps · Linux · Security

Hardening a DigitalOcean Droplet in an Afternoon

The checklist I run on every server I own for a client platform: firewall, SSH, fail2ban, WireGuard for admin access, TLS, and monitoring. Nothing exotic — just done every time.

I own the servers behind the platforms my team ships. That means when something is exposed, it is my problem, so I stopped treating hardening as a task for later and turned it into a fixed sequence I run on every new droplet before a single container starts. It takes an afternoon, and it has held up.

1. Baseline

Update everything, create a non-root user with sudo, and set the timezone and hostname. Boring, but every later step assumes it.

apt update && apt upgrade -y
adduser deploy && usermod -aG sudo

apt update && apt upgrade -y
adduser deploy && usermod -aG sudo

apt update && apt upgrade -y
adduser deploy && usermod -aG sudo

2. Firewall: deny by default

UFW with a default deny policy, then open only what the platform needs. On a typical box that is SSH, HTTP, and HTTPS. Database ports stay closed to the world; containers talk over the Docker network.

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443

One thing to know: Docker manipulates iptables directly and can bypass UFW rules for published ports. I publish container ports only on 127.0.0.1 and let Nginx be the only thing that faces the internet.

INTERACTIVE FIGURE
What a hardened droplet exposes — and what it hides

3. SSH: keys only, and fail2ban behind it

Password authentication off, root login off, keys only. Then fail2ban watches the auth log and bans repeat offenders. Even with keys only, the noise from bots is real; banning them keeps the logs readable.

# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
apt install -y fail2ban
cat > /etc/fail2ban/jail.local <<'JAIL'
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
JAIL
systemctl enable --now

apt install -y fail2ban
cat > /etc/fail2ban/jail.local <<'JAIL'
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
JAIL
systemctl enable --now

apt install -y fail2ban
cat > /etc/fail2ban/jail.local <<'JAIL'
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
JAIL
systemctl enable --now

4. WireGuard for anything administrative

Dashboards, database admin tools, monitoring — anything that is not the public site — should not be on the public internet at all. A WireGuard tunnel takes fifteen minutes to set up and moves all of it onto a private address. Nginx then serves those tools only on the WireGuard interface.

apt install -y wireguard
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
# interface on 10.8.0.1/24, one peer per team member
ufw allow 51820/udp
systemctl enable --now

apt install -y wireguard
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
# interface on 10.8.0.1/24, one peer per team member
ufw allow 51820/udp
systemctl enable --now

apt install -y wireguard
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
# interface on 10.8.0.1/24, one peer per team member
ufw allow 51820/udp
systemctl enable --now

5. TLS without thinking about it

Let’s Encrypt with automatic renewal, or Cloudflare in front when the client already uses it. Either way the rule is the same: HTTPS everywhere, HTTP only redirects, and modern ciphers in Nginx. Certificate expiry is the most common self-inflicted outage I have seen; renewal is a systemd timer, not a calendar reminder.

6. Monitoring you will actually look at

I run Beszel, a lightweight monitoring server with an agent per droplet. CPU, memory, disk, network, and container stats in one place, with alerts to the team chat. It is not Grafana, and that is the point — nobody on a fifteen-person team has time to babysit dashboards. A single page that turns red is enough.

The checklist

  • Non-root deploy user, sudo, timezone

  • UFW default deny; only 22, 80, 443 open

  • Container ports bound to 127.0.0.1; Nginx is the only public face

  • SSH keys only, no root login, fail2ban on

  • WireGuard for every admin surface

  • TLS with automatic renewal

  • Beszel agent reporting before the first deploy

None of this is clever. Its value is that it happens every time, in the same order, before the platform goes live — which is the only kind of security that survives a busy delivery team.

Written by Lasha Kakashvili — Tech Lead at Koober Coders, instructor at IT Step Academy.