Skip to content
A AhsanLab.Tech
Deployment & CI/CD 12 min read · June 27, 2026

Securing a VPS Before You Put It in Your Deploy Pipeline

A deploy pipeline puts code on a server automatically, from the internet, on every push. Building one on a box you have not hardened is a decision to automate access to something you have not secured. This is the hour of work that goes first — and the order matters more than the list.

A Ahsan Habib Save

A deploy pipeline is a machine that puts code on a server automatically, from the internet, on every push. Building one on a box you have not hardened is not a shortcut — it is a decision to automate access to something you have not secured, and the automation is the part that makes it interesting to somebody else.

This is the hour of work that goes before the pipeline. Not a comprehensive security guide; the specific things that matter because you are about to point a CI runner at this machine.

~2 minto first SSH probeon a fresh public IP
1 hrto do all of thisonce, per box
3ports open at the end22, 80, 443

That first number is not rhetorical. Bring up a VPS with a public IP and password authentication and watch /var/log/auth.log: automated credential stuffing finds it within minutes, continuously, forever. It is background radiation, and the whole point of the list below is to make it irrelevant rather than to stop it.

Order

Do these before the box does anything useful

EACH STEP ASSUMES THE ONE BEFORE IT 1 · KEYS add yours, test in a 2nd shell 2 · SSHD no passwords, no root login 3 · FIREWALL deny by default, allow 22/80/443 4 · UPDATES unattended security only 5 · DEPLOY USER forced command, narrow sudo 6 · CI now, and not before THE ORDER IS THE ADVICE disable password login before you have a working key and you have locked yourself out point CI at the box before step 5 and the runner has a root shell on your production server
FIGURE 1 — SEQUENCE · Two steps out of order produce the two failures everyone has had once.

1 & 2 — SSH, in the only safe order

/etc/ssh/sshd_config.d/hardening.conf
# Do this ONLY after you have confirmed key login works in a second terminal
# that you keep open. If you get this wrong with one session, you are locked out.
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers you deploy          # an explicit list beats any blocklist

# Cheap, and closes the "hold the connection open forever" class of abuse.
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2

sshd -t before reloading. A config error plus a reload plus one open session is the classic way to lose access to a box you have just started paying for.

On moving SSH off port 22 It removes almost all the log noise and stops exactly zero targeted attacks — a scan finds the new port in seconds. Worth doing for the quieter logs, worth nothing as a security control. Do not let it substitute for disabling password authentication, which is the change that actually matters.

3 — Firewall: deny by default

ufw — three rules
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# MySQL, Redis and your app port are NOT in that list, deliberately.
# They bind to 127.0.0.1 and are reached over an SSH tunnel when you need them.

The single most common real exposure on a small VPS is not a clever exploit — it is Redis or MySQL bound to 0.0.0.0 with a weak password because a tutorial said to make it reachable. Check what is actually listening: ss -tlnp. Anything on 0.0.0.0 that is not your web server needs a reason.

4 — Unattended security updates

security updates only, automatically
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

# In /etc/apt/apt.conf.d/50unattended-upgrades keep ONLY the security origin
# enabled. Automatic feature updates on a production box is how you find out
# your PHP minor version changed at 3am.
Unattended-Upgrade::Automatic-Reboot "false";

Automatic reboot off, deliberately: a box that reboots itself at 3am without draining connections is its own outage. Patch automatically, reboot on your schedule, and watch for the reboot-required flag.

5 — The deploy user

This is the step that exists specifically because of the pipeline, and the one people skip. Full detail is in the pipeline post; the shape is a user that owns only the app directory, a key forced to one command, and two explicit sudoers lines.

Ask what a stolen CI secret buys. If the answer is "a shell on production", the pipeline is a liability. If it is "the ability to deploy code that is already in the repository", it is just a deployment tool.

The rest

Worth an extra twenty minutes

MeasureWhat it buysWorth it?
fail2banBans repeat offenders; mostly quieter logs once passwords are offCheap, marginal
Separate app user from deploy userPHP-FPM cannot overwrite its own codeYes — stops a web exploit becoming persistence
.env at 600, owned by deployNot world-readable on a multi-user boxYes, and check it
Off-box backups, restore testedThe only control that helps after everything else failedYes, above all of this
Log shippingLogs survive the box being compromisedYes, once it matters
2FA on the hosting panelProtects the console that can reset rootYes — people harden the box and forget the panel
The one people get backwards A hardened server with no tested restore is worse positioned than a mediocre server with one. Every control on this page reduces the chance of a bad day; only the backup does anything on the day itself. Restore it somewhere real, at least once, before you trust it.

Honesty

What this list does not cover

  • Application security. Every control here is at the OS boundary. SQL injection, broken authorisation and dependency vulnerabilities walk straight past all of it.
  • Supply chain. Your pipeline installs hundreds of packages from the internet on every build. Lockfiles and pinned action SHAs are the minimum, and they are not on this page.
  • Detection. This is all prevention. Whether you would notice a compromise is a different discipline and a much harder one.
  • Compliance. If you have real obligations, this list is a starting point and not an answer.
  • Multi-tenant risk. A VPS is a shared kernel. This does not address the hypervisor.

The checklist

  • Key login working and tested in a second session before touching sshd.
  • PasswordAuthentication no and PermitRootLogin no, validated with sshd -t.
  • AllowUsers as an explicit list.
  • Deny incoming by default; only 22, 80 and 443 open.
  • ss -tlnp reviewed — nothing unexpected on 0.0.0.0.
  • Unattended security upgrades on, automatic reboot off.
  • Deploy user with a forced-command key and two sudoers lines.
  • App user separate from deploy user, so the web process cannot rewrite its own code.
  • .env at 600, owned by deploy, verified.
  • Off-box backups with a restore you have actually performed.

Where to start on Monday

Run ss -tlnp on a server you already have. If a database, a cache or an application port is listening on 0.0.0.0, that is today's job and it is more urgent than everything else on this page.

  • key-only SSH
  • AllowUsers
  • deny by default
  • ss -tlnp
  • unattended-upgrades
  • forced command
  • narrow sudo
  • tested restore

Once this is done, the pipeline and the zero-downtime deploy are the next two steps, and the CI/CD guide covers whether you wanted a VPS in the first place.

#CI/CD #Docker #VPS #System Design

Comments (0)

No comments yet

Be the first to share a thought on this article.

Join the conversation

Comments are moderated before they appear.

Keep reading

Related articles