Skip to content
DevOps best practices 5 min read

Security Best Practices

Security is not one tool you install — it is a set of habits you apply at every layer: the host (the server machine itself), the access paths (how people and apps log in), the data (what you store), the secrets (passwords and keys), and the pipeline (how code reaches production). This page is the capstone of the DevOps docs: a practical checklist you can work through top to bottom on a fresh Ubuntu server. Every item explains why it matters, because a checklist you don’t understand is one you’ll skip. We target Ubuntu 22.04/24.04 LTS throughout.

The layered model

A good way to think about security is “defence in depth” — many small walls instead of one big one. If an attacker gets past one layer (say, they steal a password), the next layer (key-only SSH, a firewall, least privilege) should still stop them. No single control below is enough on its own.

LayerWhat you protectMain controls
HostThe server OSPatching, firewall, fail2ban
AccessLogins and permissionsKey-only SSH, least privilege, sudo
DataStored informationEncryption, backups
SecretsPasswords, API keysSecret managers, never in Git
PipelineYour CI/CD (build/deploy automation)Dependency and image scanning

Host hardening

Hardening means removing or locking down anything an attacker could use. Start by keeping the system patched — most real-world breaches exploit known bugs that already have fixes.

sudo apt update && sudo apt upgrade -y
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

Output:

Configuring unattended-upgrades
Automatically download and install stable updates? Yes

unattended-upgrades automatically installs security patches, so you stay protected even when you forget to log in. Next, put up a firewall with ufw (Uncomplicated Firewall) — deny everything by default, then allow only what you need.

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

Output:

Firewall is active and enabled on system startup

A closed port can’t be attacked. If a service doesn’t need to be reachable from the internet (databases especially), do NOT open its port. Bind databases to 127.0.0.1 and reach them over SSH tunnels or a private network instead.

Block brute-force attempts with fail2ban

fail2ban watches your logs and temporarily bans IP addresses that repeatedly fail to log in. This stops automated password-guessing attacks cold.

sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit /etc/fail2ban/jail.local to enable the SSH jail:

[sshd]
enabled = true
maxretry = 4
bantime = 1h
findtime = 10m
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Output:

Status for the jail: sshd
|- Filter
|  `- Currently failed: 1
`- Actions
   `- Currently banned: 3

Access: least privilege and key-only SSH

Least privilege means every user and process gets the minimum access it needs — nothing more. If an account is compromised, the damage is limited to what that account could do. In practice: don’t run apps as root, give each service its own system user, and grant sudo only to humans who need it.

The single biggest SSH win is turning off password logins entirely and using SSH keys (a cryptographic key pair where the private half never leaves your laptop). Keys can’t be guessed the way passwords can.

ssh-copy-id deploy@your-server
sudo nano /etc/ssh/sshd_config.d/hardening.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
sudo systemctl restart ssh

Open a second terminal and confirm you can still log in before you close your current session. Locking yourself out of a cloud server with no console access is a painful, avoidable mistake.

Data: encryption and backups

Encryption protects data if the disk or a backup file is stolen. Always serve web traffic over HTTPS (encryption in transit) — get free certificates with Certbot.

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com

Backups protect you from ransomware, mistakes, and hardware failure. A backup you’ve never restored is just a hope — test it. Follow the 3-2-1 rule: 3 copies, on 2 types of media, with 1 copy off-site.

sudo -u postgres pg_dump appdb | gzip > /var/backups/appdb-$(date +%F).sql.gz
ApproachWhen to useWhen NOT to
SnapshotsWhole-server recovery, fast rollbackAs your only backup (same provider = single point of failure)
Logical dumps (pg_dump)Portable, restore to any versionVery large databases (slow)

Secrets: never in Git

A secret is anything that grants access — passwords, API keys, tokens, private keys. The classic disaster is committing one to Git, because Git history keeps it forever even after you “delete” it. Store secrets in environment files outside the repo, or in a secret manager.

echo ".env" >> .gitignore
git rm --cached .env

Scan your repo for leaked secrets before they reach a remote:

docker run --rm -v "$(pwd):/repo" zricethezav/gitleaks:latest detect --source=/repo

Output:

leaks found: 0

Pipeline: scan before you ship

Your CI/CD pipeline is the perfect place to catch problems automatically. Two scans matter most: dependency scanning (finds known-vulnerable libraries) and image scanning (finds vulnerabilities in your Docker images).

scan:
  image: aquasec/trivy:latest
  script:
    - trivy fs --exit-code 1 --severity HIGH,CRITICAL .
    - trivy image --exit-code 1 --severity CRITICAL myapp:latest

The --exit-code 1 flag fails the build when serious issues are found, so vulnerable code never gets deployed.

Best Practices

  • Patch automatically with unattended-upgrades — known, unpatched bugs cause most breaches.
  • Default-deny your firewall and only open ports a service truly needs.
  • Disable password and root SSH login; use keys and a non-root deploy user.
  • Run apps under their own unprivileged system user, never as root.
  • Keep secrets out of Git, scan for leaks, and rotate any key that may have leaked.
  • Encrypt traffic with HTTPS and test that your backups actually restore.
  • Fail the CI/CD build on HIGH/CRITICAL findings so vulnerabilities can’t ship.
Last updated June 15, 2026
Was this helpful?