A Debian-first baseline hardening guide for real systems. This is not an enterprise lockdown or a complete security framework. It is a practical checklist that reduces unnecessary exposure, tightens common defaults, and gives you a cleaner foundation for servers, homelabs, and small-business systems.
Linux hardening is the process of reducing attack surface, tightening access, and making a system harder to misuse. In practical terms, that means limiting exposed services, enforcing safer defaults, improving account security, and making sure you can still manage the system cleanly after the changes are applied.
In this guide, the focus is a Debian system that already works and now needs a stronger baseline. The goal is not to turn Debian into a complicated security project. The goal is to make it safer without breaking normal administration.
Why this matters:
• A working system is not automatically a secure one.
• Default settings are often broader than they need to be.
• Basic hardening helps reduce common abuse like brute-force attempts, unnecessary service exposure, and weak remote access practices.
• This becomes the security foundation for later builds like NAS systems, remote access servers, personal cloud platforms, and monitoring stacks.
This page uses one full baseline hardening path. It starts with safe checks, then moves into account security, firewall control, SSH tightening, service review, and persistence testing.
This is intentional. It is better to apply baseline protections in a controlled order than to stack random security changes and hope nothing breaks.
This guide improves the default security posture of a Debian system, but it does not make the system invulnerable. It does not replace network segmentation, backups, VPN design, patch management discipline, monitoring, or application-specific security work.
Most automated attacks target default configurations, weak credentials, and exposed services. This baseline directly reduces those risks, but it does not eliminate targeted or advanced threats.
Why: Hardening is a layer, not the whole strategy. Treat this as the clean starting point that everything else builds on.
• A Debian-based system installed
• Sudo access to an administrative account
• A stable local or remote console path in case you need to recover from a mistake
• A system that is already functioning normally before hardening begins
sudo apt update
sudo apt full-upgrade -y
What this does:
Refreshes Debian’s package lists and installs all currently available upgrades.
Why this matters:
There is no point hardening around outdated packages if known fixes are already available. You want your starting point to be current before you tighten anything else.
What to expect:
Package lists should refresh, updates should install if any are available, and the command should finish without package errors.
sudo reboot
What this does:
Restarts the system so kernel, service, and library updates fully take effect.
Why this matters:
A system may show updated packages while still running older components in memory. Rebooting gives you a clean baseline before continuing.
What to expect:
Your SSH session or console session will disconnect, the system will restart, and you will reconnect after it comes back up.
getent group sudo
What this does:
Shows which local accounts currently belong to the sudo group.
Why this matters:
Hardening starts with knowing which accounts already have elevated privileges. If too many accounts can administer the system, the risk is broader than it needs to be.
What to expect:
The command should return the sudo group line and list any accounts that currently have sudo access.
awk -F: '$7 !~ /(nologin|false)$/ {print $1 ":" $7}' /etc/passwd
What this does:
Lists accounts whose shells are not set to nologin or false, which helps identify users that may be able to log in interactively.
Why this matters:
You should know which accounts can actually be used to access the system. Security work is harder when you do not know what is normal.
What to expect:
You should see a short list of normal interactive accounts. Service accounts should generally not appear here.
sudo passwd -l username
What this does:
Locks the password for the specified account so it cannot be used for password-based login.
Why this matters:
Unused or legacy accounts expand your exposure. If an account does not need interactive password access, it should not remain casually usable.
What to expect:
Debian should confirm that the password was locked. Replace username with the real account name you intend to restrict.
This prevents password-based login for the account. Other authentication methods, such as SSH keys, may still function depending on configuration.
sudo apt install -y ufw fail2ban unattended-upgrades apt-listchanges
What this does:
Installs the Uncomplicated Firewall, Fail2Ban, automatic security update tooling, and package change notification support.
Why this matters:
These are practical baseline tools. UFW controls network exposure, Fail2Ban helps respond to repeated abusive login behavior, and unattended upgrades help close the gap between patch release and patch installation.
What to expect:
Debian should download and install the packages. If they are already installed, it may report that no changes were needed.
sudo ufw default deny incoming
sudo ufw default allow outgoing
What this does:
Sets UFW to deny unsolicited inbound traffic by default while still allowing normal outbound traffic from the system.
Why this matters:
This creates a clean default stance. Instead of exposing whatever happens to be listening, you explicitly allow only what you intend to use.
What to expect:
UFW should confirm that the default policies were updated.
sudo ufw allow OpenSSH
What this does:
Allows SSH traffic using UFW’s built-in OpenSSH application profile.
Why this matters:
If you manage the system remotely, you must allow SSH before enabling the firewall or you may lock yourself out.
What to expect:
UFW should confirm that the OpenSSH rule was added.
This allows SSH from any source. For more restrictive setups, limit access to specific IP ranges instead of opening it broadly.
If this machine also hosts a local web service, add only the ports you actually need. For example:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
What this does:
Opens HTTP and HTTPS traffic for web services.
Why this matters:
A firewall should support your intended service design, not fight it. Only allow these if the system actually serves web traffic.
What to expect:
UFW should confirm each new rule that gets added.
sudo ufw enable
What this does:
Activates the firewall rules you configured.
Why this matters:
Rules that are never enabled do not protect anything. This is the point where the system begins enforcing your chosen network policy.
What to expect:
UFW may warn that enabling the firewall can disrupt SSH sessions. If you already allowed OpenSSH first, your remote administration path should remain available.
sudo ufw status verbose
What this does:
Displays the current firewall state, default policy, and active allow rules.
Why this matters:
Hardening should be verified, not assumed. This confirms whether the firewall is active and whether the policy looks the way you intended.
What to expect:
You should see UFW marked as active, incoming denied by default, outgoing allowed by default, and only the specific ports you added.
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
What this does:
Creates a backup copy of your current SSH daemon configuration file.
Why this matters:
SSH is one of the most sensitive parts of hardening. A backup gives you a clean rollback point if you make a mistake.
What to expect:
The copy command should produce no output if it succeeds.
sudo nano /etc/ssh/sshd_config
What this does:
Opens the SSH daemon configuration so you can tighten baseline access settings.
Why this matters:
SSH is often the most direct administrative entry point into a Linux system. It deserves more attention than the default configuration alone.
What to expect:
Nano should open the SSH configuration file for editing.
Find or add these lines:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowUsers youradminuser
What this does:
PermitRootLogin no prevents direct root login over SSH. PasswordAuthentication no disables password-based SSH login. PubkeyAuthentication yes keeps key-based access enabled. MaxAuthTries 3 limits repeated login attempts per connection. X11Forwarding no disables a feature most servers do not need. AllowUsers youradminuser explicitly limits SSH access to the administrative user you name.
Why this matters:
This is one of the highest-value baseline changes you can make. Disabling root SSH login and removing password-based SSH access significantly reduces the usefulness of brute-force attacks.
What to expect:
The file should save normally. Replace youradminuser with the actual administrative account you intend to use.
If multiple administrative users need SSH access, list all of them on the same line separated by spaces.
Do not disable password authentication unless you have already confirmed that SSH key-based login works in a separate session.
Why: SSH hardening is valuable, but access recovery is harder if you remove your only working login path too early.
sudo /usr/sbin/sshd -t
What this does:
Checks the SSH daemon configuration file for syntax problems before you reload the service.
Why this matters:
This is a safety check. It is much better to catch a bad directive before restarting SSH than after losing remote access.
What to expect:
No output usually means the configuration is valid. Errors will point you to the line that needs correction.
sudo systemctl restart ssh
What this does:
Restarts the SSH daemon so your updated settings take effect.
Why this matters:
Editing the configuration alone changes nothing until the service reloads or restarts.
What to expect:
The restart should complete silently if successful. Your current session may stay open, but new sessions will use the new rules.
sudo systemctl status ssh
What this does:
Displays the SSH service status and recent startup details.
Why this matters:
This confirms that the service is active after your hardening changes and did not fail during restart.
What to expect:
You want to see the service marked as active and running.
sudo dpkg-reconfigure -plow unattended-upgrades
What this does:
Opens Debian’s unattended-upgrades configuration prompt so you can enable automatic package installation for allowed update sources.
Why this matters:
Patching only helps when it actually happens. Automatic security updates reduce the time your system stays exposed to already-fixed vulnerabilities.
What to expect:
A text prompt should ask whether unattended upgrades should be automatically downloaded and installed. Choose Yes.
By default, unattended-upgrades installs security updates only. Full system upgrades should still be performed manually or through a controlled maintenance process.
sudo ls /etc/apt/apt.conf.d/50unattended-upgrades
What this does:
Checks that the main unattended-upgrades configuration file is present.
Why this matters:
This confirms the package is installed and its core configuration file is available if you need to review or tune it later.
What to expect:
The command should print the file path if it exists.
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
What this does:
Configures Fail2Ban to start automatically and launches it immediately.
Why this matters:
Fail2Ban does not help if it is installed but inactive. This makes sure it is part of the system’s normal startup behavior.
What to expect:
Systemd should confirm the service was enabled, and the start command should complete without errors.
sudo nano /etc/fail2ban/jail.local
What this does:
Opens a local Fail2Ban configuration file where you can define your own settings without modifying the package defaults directly.
Why this matters:
Local override files survive package updates more cleanly and make your intended policy easier to understand later.
What to expect:
Nano should open a new or empty file if it does not already exist.
Paste this:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
What this does:
Sets a one-hour ban window, tracks repeated failed events over ten minutes, bans after five failures, uses systemd logs as the backend, and enables SSH protection.
Why this matters:
This gives you a simple, readable baseline for protecting SSH from repeated failed login attempts without introducing complicated policy tuning.
What to expect:
The file should save normally. The configuration remains inactive until Fail2Ban is restarted.
On most modern Debian systems, systemd is the correct backend. If your system uses traditional log files instead, this may need to be adjusted.
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
What this does:
Reloads Fail2Ban with your local settings, shows the overall service status, and then shows the status of the SSH jail specifically.
Why this matters:
Installing or editing a protection tool is not the same thing as confirming it is actually watching the service you care about.
What to expect:
You should see Fail2Ban active and the sshd jail listed as enabled.
sudo ss -tulpn
What this does:
Lists listening TCP and UDP sockets along with the owning processes.
Why this matters:
You cannot meaningfully reduce exposure if you do not know what is exposed. This is one of the fastest ways to see what the system is currently listening on.
What to expect:
You should see a list of listening services and their ports. Investigate anything you do not recognize or do not actually need.
sudo systemctl disable servicename
sudo systemctl stop servicename
What this does:
Prevents a service from starting automatically at boot and stops it immediately in the current session.
Why this matters:
One of the cleanest hardening actions is simply removing unnecessary exposure. A service you do not need should not stay running by habit.
What to expect:
Systemd should confirm the unit was disabled. Replace servicename with the real service you intentionally want to remove from normal operation.
systemctl --failed
What this does:
Shows services that are currently in a failed state.
Why this matters:
Hardening should not quietly damage system stability. This check helps make sure your baseline security work did not leave broken units behind.
What to expect:
Ideally the list is empty. If anything is failed, investigate it before moving on.
sudo journalctl -u ssh --since "today"
What this does:
Displays SSH-related log entries from today through the systemd journal.
Why this matters:
Basic visibility is part of hardening. You should know how to review login-related events so suspicious behavior does not go unnoticed.
What to expect:
You should see SSH service events and login-related activity for the current day.
• Open a new SSH session before closing your current one
• Confirm key-based login works if password SSH access was disabled
• Run sudo ufw status verbose and confirm only intended ports are open
• Run sudo fail2ban-client status sshd and confirm the jail is active
• Reboot the system
• Confirm SSH, UFW, and Fail2Ban all come back normally after boot
• Confirm any hosted services you intentionally use still function
Why this matters: Security changes that break administration or normal service are not complete. Hardening should reduce risk without making the system unreliable.
Do not treat this checklist as a one-time event. Baseline hardening only stays useful if the system remains updated, unnecessary services stay removed, and remote access rules are reviewed whenever the system’s purpose changes.
If you later expose the system to the public internet, add stronger controls around remote access, application security, monitoring, backups, and recovery planning.
• Harden a Debian NAS after the base system is locked down
• Harden a remote access server with the same baseline controls
• Add centralized logging and monitoring for visibility
• Build a more mature patching, backup, and recovery process