When [DeployHQ](https://www.deployhq.com) ships your code, it does so over SSH into a Linux server you own. Everything [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/blog/shared-hosting-vs-vps-a-comprehensive-guide-for-junior-developers) covers when self-managed infrastructure is worth the operational cost.

For the comprehensive reference, [imthenachoman's How To Secure A Linux Server](https://github.com/imthenachoman/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](https://www.deployhq.com), 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](https://www.deployhq.com) 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](https://www.deployhq.com) in _Servers → SSH Keys_. [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/blog/deployhq-cli-deploy-from-terminal)[DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/blog/deploy-configure-openclaw-vps) 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](https://www.deployhq.com) publishes its outbound deployment IPs in the dashboard under _Account → Server Information_. If your security model treats SSH as only [DeployHQ](https://www.deployhq.com) + 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](https://www.deployhq.com/blog/self-host-paperclip-vps-docker-deployhq) 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](https://www.deployhq.com/features/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](https://www.deployhq.com) 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](https://www.deployhq.com) projects, separate server SSH users, separate firewall rules.

**Log every deployment.** [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/blog/ci-cd-pipelines-complete-guide) 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](https://www.deployhq.com) 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](https://www.deployhq.com) control plane, deployment logs you can ship to your SIEM, and [one-click rollback](https://www.deployhq.com/features/one-click-rollback) when a release goes wrong.

If you're not yet using [DeployHQ](https://www.deployhq.com) for SSH-based deployments to your hardened Linux servers, you can [start a free trial](https://www.deployhq.com/signup) and connect your first server in under ten minutes.

* * *

Questions about hardening a specific deployment topology? Email us at [support@deployhq.com](mailto:support@deployhq.com) or [DM us on X](https://x.com/deployhq) — we read every message.

