How to Secure a VPS That Runs Docker Containers
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
A rented server is cheap. Operating one badly is not.
A VPS is a rented Linux box with a public IP address. That is the whole product — everything else is work you do, and the interesting part of that work is the second week rather than the first.
Reach for one when you want predictable cost, no vendor-specific runtime, and the ability to run anything that runs on Linux. Databases, background workers, long-lived connections and self-hosted tools all fit here and often fit poorly elsewhere.
Reach for a managed platform instead when nobody on the team wants to be responsible for kernel updates at 2 a.m., or when the workload genuinely needs to scale horizontally without warning.
The failures are consistent across every provider. Firewalls that look correct
but do not cover published container ports, because Docker manipulates iptables
directly. Disks that fill with unrotated container logs. Backups that have never
been restored. And a single administrator SSH key with no second path in, so one
misconfigured sshd_config means rebuilding the machine.
Day-two operations is the part that separates a server that survives from one that is quietly rebuilt every few months. Unattended security upgrades, monitored disk usage, log rotation, tested restores, and a firewall you have verified by scanning from somewhere else — none of it is difficult, and all of it is skipped more often than not.
The articles here assume Ubuntu LTS and Docker, state the exact versions they were run against, and verify the result from outside the host rather than from inside it.
Start here
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
A reading order that builds VPS knowledge in the sequence it is actually needed.
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.
Failure modes that come up repeatedly with VPS, and where each one is solved.
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
Keep the listener off the public internet, get pg_hba.conf right, require TLS for remote connections, and give the application a role that cannot drop your tables — on PostgreSQL 17 and Ubuntu 24.04.
A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.
Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.
Diagnose which file descriptor limit you actually hit — container, daemon or kernel — then raise it properly, and work out whether the real problem is a leak rather than a limit.
Create the A record, pick the right proxy status, set the SSL mode to Full (strict) and lock the origin so only Cloudflare can reach it — plus what errors 521, 522, 525 and 526 actually mean.
Both put a Heroku-style deploy UI on your own VPS, but on different container primitives. What that means for zero-downtime deploys, multi-server growth and the day the control plane breaks.
SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.
A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.
2 GB is the practical floor for an application plus a database, and 4 GB is comfortable once you add a reverse proxy and leave room for a container build. Below 2 GB you will spend your time debugging the OOM killer.
For a single-team application, yes, provided someone owns patching, backups and the pager. The technology is not the constraint; the operational commitment is.
A non-root user with an SSH key, password authentication disabled, a default-deny firewall, unattended security upgrades, and a check that published container ports are not bypassing the firewall.