Sanexxxx777/server-hardening-playbook

Practical Linux server hardening — every item is failure → fix → verify. SSH, firewall, service binding, secrets, DB auth, and more.

14

stars

7

commits

Shell

primary language

Sep 7, 2026

updated

shulgin.is-a.dev
devops
firewall
hardening
infosec
linux
opsec
security
self-hosted
server-hardening
ssh
sysadmin
vps

README

Server Hardening Playbook

🇷🇺 Кратко на русском

Практический чек-лист для укрепления защиты production Linux-сервера — каждый пункт по схеме проблема → как исправить → как проверить. Покрывает SSH, firewall, привязку сервисов к интерфейсам, секреты, аутентификацию БД и остальное. Полный текст — на английском ниже.

⚠️ Применяйте команды осознанно и сначала проверяйте не на боевом сервере — некоторые шаги (например отключение пароля SSH) могут заблокировать доступ, если сделаны не по порядку.

A practical, battle-tested checklist for locking down a production Linux server — written after learning some of these lessons the hard way.

Most "hardening guides" are either a wall of sysctl flags nobody applies, or a vendor benchmark you skim once and forget. This one is different: every item here maps to a real way servers get owned — an exposed port, a weak remote-desktop password, a secret in git, a database with no auth — and gives you the exact command to close it, plus how to verify it actually closed.

If you run a VPS, a side-project box, or a small fleet and you don't have a security team, this is for you.

The one lesson that matters most: a service is only as safe as the interface it listens on. A strong password on a service exposed to 0.0.0.0 is a weak setup. No password on a service bound to 127.0.0.1 behind a default-deny firewall is a strong one. Reachability beats secrecy. Start there.


The 10-minute triage

If you do nothing else, do these five. They close the most common real-world entry points.

# 1. Is anything listening on all interfaces that shouldn't be?
ss -tlnp | grep -vE '127.0.0.1|::1'

# 2. Is SSH password login disabled? (should print: no)
sshd -T | grep -i '^passwordauthentication'

# 3. Is the firewall default-deny? (should show DROP/deny policy)
sudo iptables -S INPUT | head -1        # -P INPUT DROP  ==  good

# 4. Any database answering with no auth?
redis-cli ping 2>/dev/null              # PONG with no password  ==  bad
mysql -u root -h 127.0.0.1 -e 'SELECT 1' 2>&1 | grep -q denied && echo "mysql: TCP root blocked (good)"

# 5. Any secrets committed to git?
git log -p | grep -iE 'api[_-]?key|secret|password|private[_-]?key|token' | head

Anything that comes back "wrong" above has a dedicated section below.

⚠️ Apply consciously, test before prod. These are real commands that change what's reachable on a live box — some (disabling SSH password auth, tightening firewall defaults) can lock you out if applied out of order. Confirm key-based login works before disabling passwords, and try unfamiliar steps on a disposable/staging box first, not directly on production.


Contents

#TopicThe failure it prevents
1SSHPassword brute-force → shell
2FirewallForgotten service exposed to the internet
3Service bindingApp on 0.0.0.0 reachable by anyone
4Remote desktop (VNC/RDP)Weak-password screen-share → full desktop
5SecretsKey in git / hardcoded → stolen credential
6Database authAuth-less redis / empty DB password
7PasswordsGuessable / short credentials
8Attack-surface auditDrift — a new hole opens and nobody notices
9Incident response basicsMaking a breach worse while reacting to it
10Change disciplineBreaking prod while trying to secure it
11Integrity sentinelPersistence sitting in plain sight for days
12Supply chainInstall-time code execution — the hole you open yourself

There's a condensed one-page version in CHECKLIST.md.


Philosophy

Reachability first, then auth, then everything else. The order is deliberate. A misconfigured firewall or a service on the wrong interface is worth more to an attacker than a weak password, because it turns a local problem into a remote one. Fix what's reachable before you polish what's secret.

Every control needs a verification. "I disabled password login" is a belief. sshd -T | grep passwordauthentication returning no is a fact. This playbook pairs every change with the command that proves it took effect — because the gap between "I edited the config" and "the running service actually changed" is where most hardening quietly fails. (Reloading isn't restarting; a config on disk isn't a config in memory.)

Defense in depth, not defense in one place. A database should be bound to localhost and behind a firewall and password-protected. Any single layer can fail — a firewall gets flushed, a bind address gets reverted in a deploy — and the others still hold.

Least surface. The most secure service is the one that isn't running. Before you harden something, ask whether it needs to be exposed — or exist — at all.


Who this is for

Solo developers, indie hackers, small teams, and anyone running their own boxes without a dedicated security function. It assumes a Debian/Ubuntu-family server and root (or sudo) access. The concepts transfer to any Linux; the exact commands are written for the common case.

It is not a compliance framework, a pentest methodology, or a substitute for a real security review of a high-value target. It's the practical 80% that stops the opportunistic 99% of attacks.


Contributing

Found a sharper command, a missing failure mode, or a distro-specific gotcha? PRs and issues welcome — see CONTRIBUTING.md. Keep the format: failure → fix → verify.

License

MIT — see LICENSE. Use it, fork it, ship it.


Maintained by Aleksandr Shulgin (@Aleksandr_NFA) · GitHub @Sanexxxx777

Contributors

Sanexxxx777

7 commits

Sanexxxx777/server-hardening-playbook

Practical Linux server hardening — every item is failure → fix → verify. SSH, firewall, service binding, secrets, DB auth, and more.

14

stars

7

commits

Shell

primary language

Sep 7, 2026

updated

shulgin.is-a.dev
devops
firewall
hardening
infosec
linux
opsec
security
self-hosted
server-hardening
ssh
sysadmin
vps

README

Server Hardening Playbook

🇷🇺 Кратко на русском

Практический чек-лист для укрепления защиты production Linux-сервера — каждый пункт по схеме проблема → как исправить → как проверить. Покрывает SSH, firewall, привязку сервисов к интерфейсам, секреты, аутентификацию БД и остальное. Полный текст — на английском ниже.

⚠️ Применяйте команды осознанно и сначала проверяйте не на боевом сервере — некоторые шаги (например отключение пароля SSH) могут заблокировать доступ, если сделаны не по порядку.

A practical, battle-tested checklist for locking down a production Linux server — written after learning some of these lessons the hard way.

Most "hardening guides" are either a wall of sysctl flags nobody applies, or a vendor benchmark you skim once and forget. This one is different: every item here maps to a real way servers get owned — an exposed port, a weak remote-desktop password, a secret in git, a database with no auth — and gives you the exact command to close it, plus how to verify it actually closed.

If you run a VPS, a side-project box, or a small fleet and you don't have a security team, this is for you.

The one lesson that matters most: a service is only as safe as the interface it listens on. A strong password on a service exposed to 0.0.0.0 is a weak setup. No password on a service bound to 127.0.0.1 behind a default-deny firewall is a strong one. Reachability beats secrecy. Start there.


The 10-minute triage

If you do nothing else, do these five. They close the most common real-world entry points.

# 1. Is anything listening on all interfaces that shouldn't be?
ss -tlnp | grep -vE '127.0.0.1|::1'

# 2. Is SSH password login disabled? (should print: no)
sshd -T | grep -i '^passwordauthentication'

# 3. Is the firewall default-deny? (should show DROP/deny policy)
sudo iptables -S INPUT | head -1        # -P INPUT DROP  ==  good

# 4. Any database answering with no auth?
redis-cli ping 2>/dev/null              # PONG with no password  ==  bad
mysql -u root -h 127.0.0.1 -e 'SELECT 1' 2>&1 | grep -q denied && echo "mysql: TCP root blocked (good)"

# 5. Any secrets committed to git?
git log -p | grep -iE 'api[_-]?key|secret|password|private[_-]?key|token' | head

Anything that comes back "wrong" above has a dedicated section below.

⚠️ Apply consciously, test before prod. These are real commands that change what's reachable on a live box — some (disabling SSH password auth, tightening firewall defaults) can lock you out if applied out of order. Confirm key-based login works before disabling passwords, and try unfamiliar steps on a disposable/staging box first, not directly on production.


Contents

#TopicThe failure it prevents
1SSHPassword brute-force → shell
2FirewallForgotten service exposed to the internet
3Service bindingApp on 0.0.0.0 reachable by anyone
4Remote desktop (VNC/RDP)Weak-password screen-share → full desktop
5SecretsKey in git / hardcoded → stolen credential
6Database authAuth-less redis / empty DB password
7PasswordsGuessable / short credentials
8Attack-surface auditDrift — a new hole opens and nobody notices
9Incident response basicsMaking a breach worse while reacting to it
10Change disciplineBreaking prod while trying to secure it
11Integrity sentinelPersistence sitting in plain sight for days
12Supply chainInstall-time code execution — the hole you open yourself

There's a condensed one-page version in CHECKLIST.md.


Philosophy

Reachability first, then auth, then everything else. The order is deliberate. A misconfigured firewall or a service on the wrong interface is worth more to an attacker than a weak password, because it turns a local problem into a remote one. Fix what's reachable before you polish what's secret.

Every control needs a verification. "I disabled password login" is a belief. sshd -T | grep passwordauthentication returning no is a fact. This playbook pairs every change with the command that proves it took effect — because the gap between "I edited the config" and "the running service actually changed" is where most hardening quietly fails. (Reloading isn't restarting; a config on disk isn't a config in memory.)

Defense in depth, not defense in one place. A database should be bound to localhost and behind a firewall and password-protected. Any single layer can fail — a firewall gets flushed, a bind address gets reverted in a deploy — and the others still hold.

Least surface. The most secure service is the one that isn't running. Before you harden something, ask whether it needs to be exposed — or exist — at all.


Who this is for

Solo developers, indie hackers, small teams, and anyone running their own boxes without a dedicated security function. It assumes a Debian/Ubuntu-family server and root (or sudo) access. The concepts transfer to any Linux; the exact commands are written for the common case.

It is not a compliance framework, a pentest methodology, or a substitute for a real security review of a high-value target. It's the practical 80% that stops the opportunistic 99% of attacks.


Contributing

Found a sharper command, a missing failure mode, or a distro-specific gotcha? PRs and issues welcome — see CONTRIBUTING.md. Keep the format: failure → fix → verify.

License

MIT — see LICENSE. Use it, fork it, ship it.


Maintained by Aleksandr Shulgin (@Aleksandr_NFA) · GitHub @Sanexxxx777

Contributors

Sanexxxx777

7 commits

Languages

Shell

81.4%

HTML

18.6%