How to Secure a Linux Server for Deployments: A Practical Hardening Checklist

Devops & Infrastructure and Security

How to Secure a Linux Server for Deployments: A Practical Hardening Checklist

When DeployHQ ships your code, it does so over SSH into a Linux server you own. Everything DeployHQ can do on that box — write files, run hooks, restart services — an attacker can do too if they get the same access. The deployment pipeline is only as secure as the server at the other end.

Most secure your Linux server guides treat the box as a workstation. A deployment target has a different threat profile: it accepts inbound SSH from CI systems and humans, runs long-lived processes, exposes ports to the public internet, and holds production data. This checklist focuses on that profile. If you're still deciding whether to run your own server at all, our breakdown of VPS versus shared hosting tradeoffs covers when self-managed infrastructure is worth the operational cost.

For the comprehensive reference, imthenachoman's How To Secure A Linux Server is the best community-maintained document in the space — 60+ topics from kernel sysctl flags to USB device control. We'll cover the subset that pays off most for servers used as deployment targets, then point you at the rest.

The threat model for deployment servers

Three things make deployment servers different from a generic Linux host:

  1. SSH is the front door, not a back door. Disabling SSH isn't an option — DeployHQ, CI runners, and on-call engineers all need it. Hardening focuses on who can use it and how.
  2. Long-lived processes run as service accounts. Your app server, database, and queue worker stay up for weeks. A compromised dependency or misconfigured cron job is a persistent foothold.
  3. The deploy step is privileged. Whatever user DeployHQ logs in as can usually restart services, write to web roots, and run migrations. That user is a high-value target.

The hardening below addresses all three.

1. SSH: the deployment interface

SSH is the only network service that absolutely must be exposed on a deployment target. Get this right and you've covered the largest attack surface.

Disable password authentication entirely. Even strong passwords are brute-forced eventually; SSH keys are not. In /etc/ssh/sshd_config:

PasswordAuthentication no
PermitRootLogin no
ChallengeResponseAuthentication no
UsePAM yes

Use a dedicated deploy user, not root. Create a deploy user with sudo privileges scoped to only the commands deployments need (service restarts, file ownership changes). Root logins should be impossible over SSH; if you need root for a recovery scenario, log in as deploy and sudo -i.

Use Ed25519 keys, not RSA. They're shorter, faster, and not vulnerable to the small-key attacks that affect older RSA pairs:

ssh-keygen -t ed25519 -C "deployhq-production-2026"

Add the public key to DeployHQ in Servers → SSH Keys. DeployHQ generates its own key per project — paste that into ~/.ssh/authorized_keys on the server with command= and from= restrictions if you want to lock it down further:

from="203.0.113.0/24",command="/usr/local/bin/deploy-wrapper",no-port-forwarding ssh-ed25519 AAAA...

Move SSH off port 22 — but don't pretend it's a security control. Port changes cut log noise from automated scanners dramatically, which makes real attacks easier to spot. They do not stop a targeted attacker.

If you're deploying from your terminal with the DeployHQ CLI or wiring deploys into an AI agent, the same SSH key restrictions apply — the CLI uses your local SSH agent and inherits whatever from= and command= constraints you set on the server.

If you're configuring SSH access for the first time, our walkthrough on deploying to a VPS with Nginx, SSL, and systemd hardening covers the full setup including reverse proxy and certificate management.

2. User and permission isolation

The principle: deployments should never run as the same user that owns the application's runtime. If the app gets RCE, the attacker should not also be able to overwrite the binary.

Three users, three responsibilities:

User What it can do What it cannot do
deploy Pull code, run build hooks, restart services via sudo Read app runtime data, write to log directories owned by www-data
www-data (or app) Run the web/app process, read code, write logs Modify code, restart itself
root Everything Log in over SSH

In /etc/sudoers.d/deploy, scope deploy's sudo narrowly:

deploy ALL=(root) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl reload nginx

That single line is more security than most teams ship with. deploy cannot sudo bash, cannot edit config files outside its own home, cannot read /etc/shadow.

File permissions. Code directories should be owned by deploy:www-data with 0750 on directories and 0640 on files. The runtime user can read everything; nothing can write except deploy (during deploys) and process-specific writable paths (storage/, tmp/, log dirs).

3. Firewall and network surface

Every open port is an attack surface. A deployment server typically needs three: SSH (22 or your alternate), HTTP (80), and HTTPS (443). Everything else — database, Redis, internal APIs — should bind to 127.0.0.1 or a private network interface only.

UFW configuration for a typical web deployment:

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

Allowlist DeployHQ's IPs if your network policy allows it. DeployHQ publishes its outbound deployment IPs in the dashboard under Account → Server Information. If your security model treats SSH as only DeployHQ + your jump host, ufw allow from <deployhq-ip> to any port 22 removes the rest of the internet from the attack surface for that port.

Bind databases to localhost. Check with ss -tlnp:

ss -tlnp | grep -E ':(3306|5432|6379)'

If you see 0.0.0.0:5432 instead of 127.0.0.1:5432, your Postgres is reachable from the internet. Fix it in postgresql.conf (listen_addresses = 'localhost') and restart.

If you run containerized apps, Docker bypasses UFW by writing its own iptables rules — a container started with -p 5432:5432 is publicly reachable even if ufw status says otherwise. Our walkthrough of self-hosting a Docker app on a VPS covers the network configuration to fix this (--ip 127.0.0.1 on port bindings, or a user-defined bridge network).

4. fail2ban for brute-force defense

Even with keys-only SSH, automated scanners will hammer port 22. fail2ban watches /var/log/auth.log and temporarily firewalls IPs that fail to authenticate.

apt install fail2ban

A minimal /etc/fail2ban/jail.local:

[sshd]
enabled = true
maxretry = 3
findtime = 600
bantime = 86400

[nginx-http-auth]
enabled = true

This bans IPs after 3 failed SSH attempts in 10 minutes, for 24 hours. The nginx-http-auth jail catches credential-stuffing against your app's login page if it uses HTTP basic auth.

Don't lock yourself out. Add your office IP and your CI/deployment IPs to ignoreip:

[DEFAULT]
ignoreip = 127.0.0.1/8 203.0.113.0/24 <deployhq-ip>

5. Automatic security updates

Unpatched servers are how most production breaches start. The 2017 Equifax breach was an unpatched Apache Struts vulnerability with a patch available for two months.

On Ubuntu/Debian:

apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots during a low-traffic window:

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

The tradeoff: automatic reboots interrupt service. If you run zero downtime deployments and a load balancer with at least two app servers, schedule reboots on different nights per host. If you run a single-server setup, accept a 30-second reboot once a month or set up your own staged-patching cron job — but don't leave security updates manual. They will be forgotten.

6. Intrusion detection: catching what gets through

The previous five sections raise the bar. This one tells you when someone clears it.

auditd logs syscalls and file access. The default Ubuntu config is good enough for most teams — install it and forget it:

apt install auditd
auditctl -w /etc/passwd -p wa -k passwd_changes
auditctl -w /var/www -p wa -k webroot_changes

Any write to /etc/passwd or the web root now generates a logged event. Pipe /var/log/audit/audit.log to your central logging (Datadog, Loki, ELK) and alert on passwd_changes.

AIDE (Advanced Intrusion Detection Environment) takes a cryptographic baseline of system files and tells you when they change unexpectedly:

apt install aide
aideinit
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run aide --check from cron weekly. Changes outside expected deploy paths are signal.

7. Deployment-specific considerations

These don't show up in generic hardening guides — they're specific to running a server as a deployment target.

Rotate SSH keys on team changes. When an engineer leaves, remove their key from ~/.ssh/authorized_keys on every server. Better: use a centralized SSH certificate authority (Teleport, smallstep) so revocation is one command instead of N. DeployHQ's own deploy key is per-project — rotating it is one button click in the dashboard.

Audit your build hooks. DeployHQ runs pre-build, pre-release, and post-release commands you configure. Treat those commands as production code: review them, version them, never paste from chat without checking. A malicious post-release hook is indistinguishable from a malicious deployment.

Separate staging from production with different keys. A compromised staging deployment key should not grant production access. Use separate DeployHQ projects, separate server SSH users, separate firewall rules.

Log every deployment. DeployHQ records the commit, the deploying user, and the timestamp for every deployment. Pull these into your SIEM. When something breaks at 3am, what was the last thing we shipped is the first question — and you want the answer in seconds, not minutes.

For teams running CI-driven deployments, our complete guide to CI/CD pipelines covers the upstream side: signing artifacts, secret rotation, and pipeline security boundaries that complement the server-side hardening above.

What else is worth reading

The seven topics above are the highest-ROI hardening for a deployment server. The full How To Secure A Linux Server reference (linked at the top of this article) covers another fifty topics worth knowing about:

  • 2FA on SSH via libpam-google-authenticator — adds a TOTP step on top of keys for high-value servers
  • Kernel hardening via sysctl (kernel.kptr_restrict, net.ipv4.tcp_syncookies, disabling unprivileged user namespaces)
  • AppArmor / SELinux profiles for confining your app process
  • USBGuard for physical servers (defeats the evil maid USB attack)
  • Process accounting with psacct for forensic timelines
  • Disabling kernel modules you don't use (FireWire, bluetooth, joystick on a headless server)
  • DNS hardening with unbound or DNSSEC validation

Read it once. Adopt the items that match your threat model. Skip the items that don't (USBGuard is overkill for a cloud VPS you'll never physically touch).

A minimum viable hardening checklist

If you do nothing else from this article, do these eight things:

  1. Disable SSH password auth and root login
  2. Create a dedicated deploy user with narrowly-scoped sudo
  3. Use Ed25519 keys, rotate on team changes
  4. UFW: deny inbound except 22, 80, 443
  5. Bind databases and internal services to localhost
  6. Install fail2ban with sshd jail enabled
  7. Enable unattended-upgrades with scheduled reboot
  8. Install auditd and pipe logs to your central logging

A team can do this in an afternoon. The compounded effect cuts your realistic attack surface by an order of magnitude.


Where DeployHQ fits

DeployHQ assumes the server it deploys to is yours to harden — we don't manage the OS layer. But the deploy pipeline integrates cleanly with the patterns above: per-project SSH keys, scoped deploy users, IP allowlisting from the DeployHQ control plane, deployment logs you can ship to your SIEM, and one-click rollback when a release goes wrong.

If you're not yet using DeployHQ for SSH-based deployments to your hardened Linux servers, you can start a free trial and connect your first server in under ten minutes.


Questions about hardening a specific deployment topology? Email us at support@deployhq.com or DM us on X — we read every message.