Linux Security: Hardening a Production Server
A Linux server exposed to the internet receives its first intrusion attempts within minutes of going online: port scans, SSH password guessing, and exploitation of misconfigured services. The good news is that a handful of simple measures eliminates the vast majority of these opportunistic attacks. In this guide, we harden a server step by step: SSH, firewall, fail2ban, automatic updates, permissions, auditing, and port monitoring. Each section contains the exact commands, tested on Debian/Ubuntu and Rocky Linux/Fedora. A summary checklist closes the article.
1. SSH Hardening
SSH is the main entry point to your server, and therefore the number one target. Three measures are essential: key-based authentication, disabling root login, and disabling passwords.
Key-Based Authentication
# Sur votre machine locale : générer une paire de clés ed25519
ssh-keygen -t ed25519 -C "admin@poste-travail"
# Copier la clé publique sur le serveur
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@serveur.exemple.com
# Vérifier la connexion par clé AVANT de désactiver les mots de passe
ssh -i ~/.ssh/id_ed25519 admin@serveur.exemple.com
SSH Daemon Configuration
Edit /etc/ssh/sshd_config (or better, create a file in /etc/ssh/sshd_config.d/):
# /etc/ssh/sshd_config.d/10-durcissement.conf
# Interdire la connexion directe en root
PermitRootLogin no
# Désactiver l'authentification par mot de passe : clés uniquement
PasswordAuthentication no
KbdInteractiveAuthentication no
# Limiter les utilisateurs autorisés à se connecter
AllowUsers admin deploy
# Réduire la fenêtre d'authentification et les tentatives
LoginGraceTime 30
MaxAuthTries 3
# Déconnecter les sessions inactives après 15 minutes
ClientAliveInterval 300
ClientAliveCountMax 3
# Valider la syntaxe avant de recharger : une erreur peut vous enfermer dehors
sudo sshd -t
# Recharger la configuration
sudo systemctl reload ssh # Debian/Ubuntu
sudo systemctl reload sshd # Rocky/Fedora
Always keep one SSH session open while you test the new configuration in a second terminal. If the new session fails, you can fix the problem from the first one.
Changing the Port: Useful but Not Sufficient
Moving SSH from port 22 to a non-standard port does not protect against a targeted attacker, but it drastically reduces the noise from automated scans in your logs.
# Dans /etc/ssh/sshd_config.d/10-durcissement.conf
Port 2222
Don’t forget to open this port in the firewall before reloading SSH, and to specify it when connecting afterward: ssh -p 2222 admin@serveur.
2. Firewall: ufw and firewalld
The principle: block all inbound traffic by default, then open only the ports you need.
ufw (Debian/Ubuntu)
# Installer et définir les politiques par défaut
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
# IMPÉRATIF : autoriser SSH avant d'activer le pare-feu
sudo ufw allow 2222/tcp comment 'SSH'
# Ouvrir les ports web
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Restreindre un port à une adresse IP source (ex. base de données)
sudo ufw allow from 10.0.0.5 to any port 5432 proto tcp
# Activer le pare-feu
sudo ufw enable
# Vérifier les règles avec leurs numéros
sudo ufw status numbered
# Supprimer une règle par son numéro
sudo ufw delete 3
firewalld (Rocky Linux/Fedora)
# Vérifier que firewalld est actif
sudo systemctl enable --now firewalld
# Lister la configuration de la zone active
sudo firewall-cmd --list-all
# Ouvrir des services de façon permanente
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Ouvrir un port personnalisé
sudo firewall-cmd --permanent --add-port=2222/tcp
# Retirer un service ouvert par défaut si inutile
sudo firewall-cmd --permanent --remove-service=cockpit
# Appliquer les changements permanents
sudo firewall-cmd --reload
3. fail2ban: Banning Attackers
fail2ban reads logs, detects repeated authentication attempts, and bans the offending IP address through the firewall.
# Installation
sudo apt install fail2ban # Debian/Ubuntu
sudo dnf install fail2ban # Rocky/Fedora
Never edit jail.conf: create jail.local, which overrides it.
# /etc/fail2ban/jail.local
[DEFAULT]
# Bannir 1 heure après 5 échecs en 10 minutes
bantime = 1h
findtime = 10m
maxretry = 5
# Ne jamais bannir vos propres adresses
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
[sshd]
enabled = true
port = 2222
# Activer et vérifier
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
# Débannir une adresse par erreur
sudo fail2ban-client set sshd unbanip 198.51.100.7
# Durcissement progressif : doubler la durée à chaque récidive
# (à ajouter dans [DEFAULT] de jail.local)
# bantime.increment = true
# bantime.maxtime = 1w
4. Automatic Updates
Most compromises exploit vulnerabilities that have already been patched. At a minimum, automate security updates.
Debian/Ubuntu: unattended-upgrades
# Installation et activation
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# Vérifier la configuration
cat /etc/apt/apt.conf.d/20auto-upgrades
# APT::Periodic::Update-Package-Lists "1";
# APT::Periodic::Unattended-Upgrade "1";
# Tester en simulation
sudo unattended-upgrade --dry-run --debug
In /etc/apt/apt.conf.d/50unattended-upgrades, you can enable automatic reboots during off-peak hours:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Rocky/Fedora: dnf-automatic
sudo dnf install dnf-automatic
# Dans /etc/dnf/automatic.conf : appliquer uniquement la sécurité
# upgrade_type = security
# apply_updates = yes
sudo systemctl enable --now dnf-automatic.timer
5. Permissions and umask
Checking Sensitive Files
# Les fichiers de mots de passe doivent être strictement protégés
ls -l /etc/shadow
# Attendu : -rw-r----- root shadow (640) ou plus restrictif
# Trouver les fichiers world-writable (dangereux)
sudo find / -xdev -type f -perm -0002 -not -path "/proc/*" 2>/dev/null
# Trouver les binaires setuid (à auditer régulièrement)
sudo find / -xdev -type f -perm -4000 2>/dev/null
# Les clés SSH privées doivent être en 600
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh
umask: Default Permissions
The umask determines which permissions are removed from newly created files. With a umask of 022, a file is created as 644; with 027, as 640 (no access for “others”).
# Vérifier le umask courant
umask
# Durcir pour les nouveaux fichiers : ajouter dans /etc/profile.d/umask.sh
echo "umask 027" | sudo tee /etc/profile.d/umask.sh
| umask | Created file | Created directory | Use case |
|---|---|---|---|
| 022 | 644 (rw-r—r—) | 755 (rwxr-xr-x) | Typical default value |
| 027 | 640 (rw-r-----) | 750 (rwxr-x---) | Recommended for a hardened server |
| 077 | 600 (rw-------) | 700 (rwx------) | Strictly private data |
6. auditd: Tracking Sensitive Actions
auditd records system events at the kernel level: access to critical files, system calls, and command executions.
# Installation
sudo apt install auditd audispd-plugins # Debian/Ubuntu
sudo dnf install audit # Rocky/Fedora
sudo systemctl enable --now auditd
Useful rules in /etc/audit/rules.d/durcissement.rules:
# Surveiller toute modification des comptes et de sudo
-w /etc/passwd -p wa -k comptes
-w /etc/shadow -p wa -k comptes
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Surveiller la configuration SSH
-w /etc/ssh/sshd_config -p wa -k sshd
# Tracer les commandes exécutées en root (architecture 64 bits)
-a always,exit -F arch=b64 -F euid=0 -S execve -k commandes_root
# Charger les règles et vérifier
sudo augenrules --load
sudo auditctl -l
# Rechercher les événements par clé
sudo ausearch -k sudoers --start today
# Rapport résumé des authentifications
sudo aureport -au --summary
7. Port Monitoring: ss and nmap
Internal View with ss
# Tous les ports TCP et UDP en écoute, avec le processus associé
sudo ss -tulpn
# Uniquement les ports TCP en écoute
sudo ss -tlnp
# Connexions établies vers l'extérieur
ss -tn state established
Each line of ss -tulpn should correspond to a service that you know and have deliberately installed. An unknown listening port deserves immediate investigation.
External View with nmap
Scan your server from another machine to see what an attacker actually sees:
# Scan des 1000 ports TCP les plus courants
nmap serveur.exemple.com
# Scan complet de tous les ports TCP avec détection de version
nmap -p- -sV serveur.exemple.com
# Scan UDP des ports courants (lent, nécessite root)
sudo nmap -sU --top-ports 50 serveur.exemple.com
Only scan machines that you own or for which you have written authorization.
Disabling Unnecessary Services
# Lister les services actifs
systemctl list-units --type=service --state=running
# Désactiver et arrêter un service superflu
sudo systemctl disable --now cups
8. The Principle of Least Privilege
Every process, user, and service should have only the rights strictly necessary for its function.
# Créer un utilisateur système sans shell pour un service applicatif
sudo useradd --system --no-create-home --shell /usr/sbin/nologin monapp
# Limiter sudo à une commande précise plutôt que ALL
# Dans /etc/sudoers.d/deploy (via visudo -f) :
# deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart monapp
At the systemd level, sandbox your services in their unit file:
# Extrait de /etc/systemd/system/monapp.service
[Service]
User=monapp
# Interdire l'acquisition de nouveaux privilèges
NoNewPrivileges=true
# Système de fichiers en lecture seule sauf exceptions
ProtectSystem=strict
ReadWritePaths=/var/lib/monapp
# /home inaccessible
ProtectHome=true
# /tmp privé
PrivateTmp=true
# Mesurer l'exposition d'un service (score de 0 à 10, plus bas = mieux)
systemd-analyze security monapp.service
Finally, keep SELinux (Rocky/Fedora) or AppArmor (Debian/Ubuntu) enabled: they confine compromised services.
# Vérifier l'état de SELinux
getenforce
# Attendu : Enforcing
# Vérifier l'état d'AppArmor
sudo aa-status
9. Hardening Checklist
| Measure | Verification command | Expected state |
|---|---|---|
| SSH keys only | sudo sshd -T | grep passwordauthentication | passwordauthentication no |
| Root login disabled | sudo sshd -T | grep permitrootlogin | permitrootlogin no |
| Firewall active | sudo ufw status or sudo firewall-cmd --state | active / running |
| Inbound blocked by default | sudo ufw status verbose | deny (incoming) |
| fail2ban running | sudo fail2ban-client status sshd | sshd jail active |
| Automatic security updates | systemctl status unattended-upgrades or dnf-automatic.timer | active |
| No unexpected ports | sudo ss -tulpn | only known services |
| No world-writable files | find / -xdev -type f -perm -0002 | empty list |
| Hardened umask | umask | 0027 |
| auditd running | sudo auditctl -l | rules loaded |
| SELinux/AppArmor active | getenforce / aa-status | Enforcing / profiles loaded |
| Unnecessary services disabled | systemctl list-units --type=service --state=running | minimal list |
Best Practices
- Test every SSH change in a second session before closing the first one: it is your safety net against locking yourself out.
- Open the SSH port in the firewall before enabling the firewall, never after.
- Add your administration IP addresses to fail2ban’s
ignoreipso you never ban yourself. - Never disable SELinux or AppArmor to “fix” a problem: fix the policy instead.
- Audit listening ports (
ss -tulpn) after every software installation. - Rerun the full checklist after every major system upgrade.
- Ship your logs to a remote server: an attacker who gains root will erase local traces.
- Security is a process, not a state: schedule a quarterly review of this checklist.
Conclusion
Hardening a Linux server requires neither exotic tools nor a budget: SSH restricted to keys, an allowlist firewall, fail2ban, automatic updates, strict permissions, auditd, and regular port reviews are enough to ward off nearly all the opportunistic attacks that constantly sweep the internet. The common thread running through all of these measures is the principle of least privilege: every access that is not essential is an access to remove.
Apply the checklist from section 9 to each of your servers, automate it in your provisioning scripts, and you will start from a solid baseline that advanced measures (fine-tuned SELinux, centralized logging, intrusion detection) can then reinforce.
About InSkillCoach
Training and technology expert
Coach specializing in advanced technologies and AI, backed by GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Google Cloud certifications
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion