Scope

Applies to any new Linux server intended to handle production traffic or production data: web application, API backend, WordPress site, internal tool, email server, jump host. Work top-to-bottom — items reference the guides for the “how”; this checklist is the “have I done it” layer.

Compliance references map only where they actually apply. They are not padded with vague mappings, and they are not legal advice — for a formal audit, use your certifying body’s control set as the authoritative source.

1. Before you provision

  • Hosting region picked. EEA region by default when data processing falls under EU jurisdiction. GDPR: Article 44+.
  • Provider DPA signed. Verify a Data Processing Agreement is published and executed before traffic flows. GDPR: Article 28.
  • Backup target chosen and EEA-aware. Same region preferred; encrypted at rest. See postgres-backups.
  • Logging / monitoring stack chosen. Retrofitting log minimisation after launch is painful — decide now.
  • Privacy posture decided. Worked through privacy-by-design-server-build: log streams, retention windows, what is masked or hashed. GDPR: Article 25.

2. First-boot hardening

  • Provider snapshot taken. Fresh image, pre-changes — restoring is faster than diagnosing.
  • Per-human operator account created. Not root, not ubuntu, never a shared account. NIS2: 21(2)(i); ISO 27001: A.5.16.
  • SSH key-only authentication. PasswordAuthentication no, PermitRootLogin no, AllowGroups sudo (Debian) or wheel (RHEL). See ssh-hardening. NIS2: 21(2)(e),(j); ISO 27001: A.8.5.
  • Modern SSH keys in use. Ed25519 with passphrase; the authorized_keys entries carry dated comments.
  • OS baseline applied. Run through ubuntu-baseline or rhel-baseline. NIS2: 21(2)(g).
  • Host firewall enabled. ufw or firewalld, default-deny, explicit allows. NIS2: 21(2)(e); ISO 27001: A.8.20.
  • Automatic security updates configured. With a documented reboot policy. NIS2: 21(2)(c); ISO 27001: A.8.8.
  • Time sync working. chrony enabled and reporting healthy sources.
  • auditd enabled with baseline ruleset and forwarded off-host. NIS2: 21(2)(b); ISO 27001: A.8.16.
  • AppArmor / SELinux in enforcing mode. Verified, not disabled.
  • Unused services removed. cups, bluetooth, avahi, postfix (if not sending mail), etc.

3. TLS and edge

  • Certificate issued. ECDSA P-256 via Let’s Encrypt by default. NIS2: 21(2)(h); ISO 27001: A.8.24.
  • Nginx TLS baseline applied. TLS 1.2/1.3 only, OCSP stapling, HSTS, modern ciphers — see nginx-tls-2026.
  • HSTS deployed without preload initially. Add preload only after stable operation and after auditing every subdomain.
  • Rate limiting in place on authentication and signup endpoints. See nginx-ratelimit.
  • External TLS scan passed. SSL Labs grade A or A+; document any deviation.

4. Data tier

  • PostgreSQL hardened, if used. postgres-audit returns zero FAILs. See postgresql-hardening. NIS2: 21(2)(e),(h).
  • Redis hardened, if used. redis-check returns zero FAILs. See redis-hardening.
  • Application database role is not superuser. Least-privilege grants only.
  • TLS between application and database. Mandatory across hosts; recommended on loopback.

5. Backups

  • Daily logical + weekly base backup scheduled. See postgres-backups. NIS2: 21(2)(c); ISO 27001: A.8.13.
  • Off-site, encrypted backup verified. restic / borg to an EEA-region S3-compatible endpoint. GDPR: Article 32(1)(c).
  • Backup passphrase stored off-server. Password manager that survives loss of the production server; at least two humans know it.
  • Restoration drill performed. Documented date and time-to-restore. A backup that has never been restored is not a backup.

6. Application layer

  • Production secrets in a secrets manager. Not in .env files on disk or in environment variables visible via ps.
  • Application logs do not contain personal data in literals. Verified against a representative log sample.
  • Health endpoint not publicly enumerable. Internal IP allowlist or token-gated.
  • Stack traces not exposed in production error pages.

7. Monitoring and alerting

  • At least one alert channel a human actually reads. Email, Slack, PagerDuty — does not matter which.
  • Alert on SSH authentication failures above threshold.
  • Alert on sudo use.
  • Alert on backup failure. Silent crontab failure is the classic gap.
  • Alert on TLS certificate expiry < 21 days. Safety net even with auto-renewal.
  • Out-of-band incident comms channel agreed. A Signal group, a pager number — anything that does not depend on the systems an incident may have taken down.

8. Documentation and final checks

  • Baseline applied is documented. Which guides, which version, which date. NIS2: 21(2)(a).
  • Asset inventory updated. Hostname, role, region, owner. ISO 27001: A.5.9.
  • Processor / supplier register updated. Hosting, CDN, backup target, monitoring SaaS — with DPA references. GDPR: Article 28.
  • Incident response contacts current. On-call rotation, escalation, out-of-band comms channel.
  • Privacy notice reflects what this server processes. Including the log retention period.
  • lynis audit system or equivalent run. WARNINGs reviewed and either fixed or documented as accepted.
  • Post-hardening snapshot taken. Restore-ready baseline.

Notes

The aim of this list is “defensible at launch” — not “perfect”. The larger value is in quarterly re-runs against the same items: plugin drift, retention drift, key drift, and account creep are caught by the review, not by the initial setup.

If you run more than a handful of servers, treat this list as the content and a configuration management tool — Ansible, NixOS, salt — as the enforcement. A list of checked boxes does not survive the third new server you build at three in the morning.