Skip to main content

Hardening Linux Servers for Production — Security Baseline Checklist

Apply a production security hardening baseline to Linux servers covering SSH, users, packages, kernel parameters, fail2ban, and automated compliance scanning with lynis.

30 Terms

Overview and What You Will Learn

A default Linux server installation is not production-ready from a security perspective. Root SSH is enabled. Password authentication is allowed. Unnecessary packages are installed. No rate limiting exists on login attempts. Kernel parameters use defaults that do not reflect production security requirements.

By the end of this lab you will:

  • Apply SSH hardening that eliminates the most common attack vectors
  • Configure user accounts following least-privilege principles
  • Remove unnecessary packages and services to reduce the attack surface
  • Apply kernel security parameters via sysctl
  • Deploy fail2ban to automatically block brute-force attempts
  • Run lynis to get an automated security score and remediation list

Why This Matters in Production

Zerodha operates under SEBI regulations. Every server handling trading data must meet security baselines. An unpatched server with default configurations is not just a security risk — it is a compliance failure that can result in regulatory action. The checklist in this topic is the starting point for any server that touches production workloads or regulated data.

Core Principles

Defence in depth — multiple layers:

◈ DIAGRAM
+------------------------------------------+
| Layer 1: Network (firewall, VPC, SG) |
| Block unwanted traffic before it arrives |
+------------------------------------------+
| Layer 2: SSH (key auth, no root, MFA) |
| Secure the entry point |
+------------------------------------------+
| Layer 3: Users (least privilege, audit) |
| Limit what authenticated users can do |
+------------------------------------------+
| Layer 4: OS (packages, kernel, sysctl) |
| Minimise the attack surface |
+------------------------------------------+
| Layer 5: Detection (fail2ban, auditd) |
| Detect and respond to attacks in flight |
+------------------------------------------+
| Layer 6: Compliance (lynis, CIS) |
| Measure and track security posture |
+------------------------------------------+

Detailed Step-by-Step Practical Lab

Milestone 1 — SSH hardening

SSH hardening is the highest-impact single action for server security. Disabling password authentication eliminates brute-force attacks entirely.

Bash
## Backup the original config first
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.original
## Apply production SSH hardening
sudo tee /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
## Disable root login completely
PermitRootLogin no
## Disable password authentication -- keys only
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
## Disable PAM if not needed (reduces attack surface)
## UsePAM yes <- keep yes if using PAM-based 2FA
## Allow only specific users
AllowUsers rahul deploy jenkins monitoring-agent
## Limit authentication attempts
MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30
## Disconnect idle sessions
ClientAliveInterval 300
ClientAliveCountMax 2
## Disable X11 forwarding (servers do not need it)
X11Forwarding no
## Disable agent forwarding by default (enable per-user in Match blocks if needed)
AllowAgentForwarding no
## Use only modern key exchange algorithms
KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512
## Restrict ciphers to strong ones only
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
## Verbose logging for audit trail
LogLevel VERBOSE
## Disable TCP forwarding for regular users (enable in Match blocks if needed)
AllowTcpForwarding no
EOF
## Validate config syntax before applying
sudo sshd -t
## No output = valid
## Apply changes
sudo systemctl reload sshd
## IMPORTANT: Test from a SECOND terminal before closing this session
ssh rahul@$(hostname -I | awk '{print $1}')
## If this works, your hardening is correct

Milestone 2 — User and sudo hygiene

Bash
## Audit all accounts that can log in
grep -v '/nologin\|/false' /etc/passwd | cut -d: -f1,7
## Only accounts with real shells should be here
## Remove any unexpected accounts
## Audit accounts with sudo access
sudo grep -r 'ALL' /etc/sudoers /etc/sudoers.d/ 2>/dev/null
## Remove NOPASSWD: ALL from any user that does not absolutely require it
## Jenkins CI runner might need it -- but scope it to specific commands
sudo visudo -f /etc/sudoers.d/jenkins
## jenkins ALL=(ALL) NOPASSWD: /bin/systemctl restart payment-api, /bin/systemctl reload nginx
## Lock accounts that should never log in
## (service accounts, old employee accounts)
sudo passwd --lock old-employee-account
sudo usermod --shell /sbin/nologin old-employee-account
## Find accounts with no password set (critical security issue)
sudo awk -F: '($2 == "" || $2 == "!") {print $1}' /etc/shadow
## Check password expiry policy
sudo chage -l rahul
## Minimum: 0 days, Maximum: 99999 days, Warning: 7 days
## Set password expiry for accounts using passwords
sudo chage -M 90 -W 14 rahul ## expire every 90 days, warn 14 days before
## Review last logins
last | head -20
lastlog | grep -v 'Never' ## users who have actually logged in

Milestone 3 — Package and service minimisation

Bash
## List all installed packages
dpkg -l | wc -l ## Debian/Ubuntu
rpm -qa | wc -l ## RHEL/Amazon Linux
## Find and remove services not needed on this server
## Common unnecessary services on cloud servers:
sudo systemctl list-units --type=service --state=running
## Disable and remove common unnecessary packages
sudo apt remove --purge telnet ## use SSH instead
sudo apt remove --purge rsh-client ## old, insecure remote shell
sudo apt remove --purge ftp ## use sftp instead
sudo apt remove --purge sendmail postfix ## if not a mail server
sudo apt autoremove
## Disable unnecessary services
sudo systemctl disable --now avahi-daemon ## mDNS -- not needed on servers
sudo systemctl disable --now cups ## printing -- not needed on servers
## List all listening services and evaluate each one
ss -tulpn
## For every port listed: do we need this exposed?
## If not: stop the service or bind it to localhost
## Bind services to localhost where external access is not needed
## Example: PostgreSQL should only accept local connections
grep listen_addresses /etc/postgresql/*/main/postgresql.conf
## Change to: listen_addresses = 'localhost'

Milestone 4 — Kernel hardening with sysctl

sysctl parameters control kernel behaviour. These settings harden the network stack and restrict information leakage.

Bash
## Create a production hardening sysctl config
sudo tee /etc/sysctl.d/99-hardening.conf << 'EOF'
## -- Network Security --------------------------------------
## Disable IP forwarding (enable only on routers/VPN servers)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
## Protect against SYN flood attacks
net.ipv4.tcp_syncookies = 1
## Ignore ICMP redirects (prevent route manipulation)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
## Ignore broadcast pings (smurf attack prevention)
net.ipv4.icmp_echo_ignore_broadcasts = 1
## Log suspicious packets (spoofed, source-routed, redirects)
net.ipv4.conf.all.log_martians = 1
## Disable source routing
net.ipv4.conf.all.accept_source_route = 0
## -- Kernel Security ---------------------------------------
## Restrict access to kernel logs
kernel.dmesg_restrict = 1
## Restrict kernel pointer exposure
kernel.kptr_restrict = 2
## Disable magic SysRq key (can be used to crash server)
kernel.sysrq = 0
## Restrict ptrace to parent processes only
kernel.yama.ptrace_scope = 1
## -- Memory Security ---------------------------------------
## Randomise memory layout (ASLR -- makes exploits harder)
kernel.randomize_va_space = 2
EOF
## Apply immediately
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
## Verify settings took effect
sysctl net.ipv4.tcp_syncookies
## net.ipv4.tcp_syncookies = 1

Milestone 5 — Deploy fail2ban for brute-force protection

fail2ban monitors log files and automatically bans IPs that show signs of brute-force attacks.

Bash
## Install fail2ban
sudo apt install fail2ban
## Create local configuration (never edit main jail.conf)
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
## Ban for 1 hour after 5 failures in 10 minutes
bantime = 3600
findtime = 600
maxretry = 5
## Use iptables for banning
banaction = iptables-multiport
## Email alerts (if mail server configured)
destemail = ops@devops-network.in
sendername = fail2ban
action = %(action_mwl)s
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400 ## 24 hours for SSH failures (stricter)
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
EOF
## Enable and start fail2ban
sudo systemctl enable --now fail2ban
## Check status
sudo fail2ban-client status
## Status
## |- Number of jail: 2
## `- Jail list: sshd, nginx-http-auth
## Check SSH jail specifically
sudo fail2ban-client status sshd
## Status for the jail: sshd
## |- Filter
## | `- Number of recently found: 3
## `- Actions
## |- Currently banned: 1
## `- Banned IP list: 192.0.2.45
## Manually unban an IP
sudo fail2ban-client set sshd unbanip 192.0.2.45

Milestone 6 — Automated compliance scan with lynis

lynis audits the server against security best practices and produces a prioritised list of improvements.

Bash
## Install lynis
sudo apt install lynis
## Or install from source for latest version:
## git clone https://github.com/CISOfy/lynis
## cd lynis && sudo ./lynis audit system
## Run full system audit
sudo lynis audit system
## Key output sections:
## [+] System tools <- checks installed tools
## [+] Boot and services <- checks running services
## [+] SSH <- SSH configuration score
## [+] Users, groups <- account security
## [+] Networking <- network configuration
## Hardening index at the end:
## Hardening index : 67 [############# ]
## Tests performed: 257
## Plugins enabled: 0
##
## Components:
## - Firewall [V]
## - Malware scanner [X] <- not installed
## - File integrity tool [X] <- not installed
## View all warnings found
sudo lynis audit system 2>&1 | grep Warning
## View all suggestions (prioritised list of improvements)
sudo lynis audit system 2>&1 | grep Suggestion | head -20
## Save report for compliance documentation
sudo lynis audit system --report-file /var/log/lynis-report.dat
sudo lynis show details /var/log/lynis-report.dat

Production Best Practices and Common Pitfalls

Area Wrong Correct
SSH Root login enabled, password auth on PermitRootLogin no, PasswordAuthentication no
Users Shared accounts, NOPASSWD: ALL Named accounts, scoped sudo commands
Packages Never remove anything Audit and remove unused services quarterly
Kernel Default sysctl settings Apply hardening.conf with network protections
Monitoring No failed login visibility fail2ban + auth.log monitoring

Quick Reference and Troubleshooting Commands

Task Command
Validate sshd config sudo sshd -t
Check who has sudo sudo grep -r ALL /etc/sudoers.d/
List listening services ss -tulpn
Apply sysctl sudo sysctl -p /etc/sysctl.d/99-hardening.conf
fail2ban status sudo fail2ban-client status sshd
Unban an IP sudo fail2ban-client set sshd unbanip IP
Run lynis audit sudo lynis audit system
Check auth logs `grep 'Failed|Invalid' /var/log/auth.log
Tip

Run sudo lynis audit system on every new server before it goes into production. The hardening index score and suggestion list give you a concrete, prioritised remediation plan. Aim for a hardening index above 80 for production servers handling sensitive data.

Remember

Security hardening is not a one-time task. New CVEs are discovered regularly. Enable unattended security upgrades on every server, schedule quarterly lynis scans, and review sudo access lists whenever team members join or leave.

Security

Never disable UsePAM yes in sshd_config unless you are absolutely certain you do not need PAM-based authentication. Disabling PAM can break two-factor authentication, account locking, and session management. If hardening SSH algorithms or disabling password auth, leave UsePAM yes in place.

Common Mistake

Applying PasswordAuthentication no before verifying that key-based authentication works. If you lock out password auth and your key is not in authorized_keys, you need console access to recover. Always test key login from a second terminal before disabling passwords.

Resources

Explore More in Linux Networking and Security

All 4 Topics

Frequently Asked Questions

Is Hardening Linux Servers for Production — Security Baseline Checklist free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Hardening Linux Servers for Production — Security Baseline Checklist topic cover?

Apply a production security hardening baseline to Linux servers covering SSH, users, packages, kernel parameters, fail2ban, and automated compliance scanning with lynis.