A practical hardening guide for your Linux VPS, from CrowdSec to the kernel
A full review of the security measures you can apply to a Linux VPS, from CrowdSec and the firewall to kernel hardening, including SSH, Docker, and automatic updates.

Having a VPS exposed to the internet is like leaving your front door on a very busy street. Locking it isn't enough, it's worth reinforcing the lock, installing an alarm, and making sure the windows are shut too. In this article I go over the security layers I consider essential for any production Linux server.
CrowdSec: collaborative security
If you're coming from Fail2Ban, CrowdSec is the next natural step. While Fail2Ban is limited to looking for regex patterns in your server logs, CrowdSec analyzes behavior and, more interestingly, shares threat intelligence with its whole user community.
How it works
The architecture is split into three main pieces:
- The Security Engine reads logs, processes them with YAML parsers, and applies behavioral detection scenarios. It doesn't look for a specific line, but for patterns like "this IP has generated 200 HTTP requests in 10 seconds when the normal number is 2".
- The LAPI (Local API) is the local control center that manages blocking decisions, anonymizes information before sharing it, and receives global intelligence.
- Bouncers are remediation modules that act on those decisions. There are ones for nftables, iptables, Nginx, Traefik, Cloudflare, and more.
Basic installation
# Instalar CrowdSec
curl -s https://install.crowdsec.net | sudo bash
sudo apt install crowdsec
# Instalar bouncer de firewall (nftables)
sudo apt install crowdsec-firewall-bouncer-nftables
sudo systemctl enable crowdsec-firewall-bouncer
# Instalar colecciones de detección
sudo cscli collections install crowdsecurity/sshd
sudo cscli collections install crowdsecurity/linux
sudo cscli collections install crowdsecurity/base-http-scenarios
# Verificar estado
sudo cscli metrics
sudo cscli bouncers list
sudo cscli decisions listWhy not Fail2Ban?
The key difference is scope. Fail2Ban is reactive and local, it only protects your server with what it sees in your logs. CrowdSec is collaborative: when one user detects an attack, the signal is sent anonymously to the consensus engine, which cross-checks it against reports from other nodes. The result is a verified community blocklist that protects you before the attacker gets to your door.
On top of that, CrowdSec offers more granular responses. Not everything has to be a ban, you can serve CAPTCHAs or apply throttling depending on how serious the scenario is.
SSH: the first line of defense
SSH is the main administration entry point for any VPS and, because of that, one of the most heavily attacked targets. Basic hardening should include at least these points:
# /etc/ssh/sshd_config
# Cambiar el puerto por defecto
Port 2222
# Solo claves, nunca contraseñas
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Sin acceso root
PermitRootLogin no
# Limitar intentos y sesiones
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
# Desactivar todo lo innecesario
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
# Timeouts de sesión
ClientAliveInterval 300
ClientAliveCountMax 2
# Solo usuarios específicos
AllowUsers tuusuario
# Nivel de log detallado
LogLevel VERBOSEFor keys, Ed25519 is the recommended option today. It's faster, more secure, and generates shorter keys than RSA:
ssh-keygen -t ed25519 -a 100 -C "[email protected]"If your version of OpenSSH supports it (10.0+), you can also add mlkem768x25519-sha256 to the key exchange algorithms for post-quantum protection.
Firewall: deny everything by default
The golden rule is simple: deny all incoming traffic and allow only what's strictly necessary.
# Políticas por defecto
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH con rate limiting
sudo ufw limit 2222/tcp comment 'SSH rate limited'
# Tráfico web
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Activar
sudo ufw enableDocker's firewall trap
This is one of those details that catches a lot of people off guard: Docker writes iptables rules directly, completely bypassing UFW. If you publish a port with -p 8080:80, your UFW rules won't apply to that container.
The most common fixes are binding to localhost (-p 127.0.0.1:8080:80), managing rules in the DOCKER-USER chain in iptables/nftables, or using a reverse proxy like Traefik that only exposes ports 80 and 443.
Automatic security updates
A server without security patches is a server with an expiration date. unattended-upgrades takes care of applying security patches automatically:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgradesThere are quite a few things you can tune in the configuration: which origins get updated, whether it reboots automatically when the kernel requires it, email notifications, and excluded packages. What matters is that system security patches get applied without manual intervention.
# Verificar que funciona
sudo unattended-upgrade -v --dry-runDocker: reducing the attack surface
If your VPS runs containers, there are several measures that should be standard:
Never run as root
RUN groupadd --system --gid 1001 appgroup && \
useradd --system --uid 1001 --gid appgroup appuser
COPY --chown=appuser:appgroup . /app
USER appuserRead-only filesystem and minimal capabilities
services:
app:
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid
cap_drop:
- ALL
security_opt:
- no-new-privileges:trueIsolated networks
Not every container needs to talk to each other or access the internet. Split networks by function:
networks:
frontend-net: {}
backend-net:
internal: true # Sin acceso a internetThe Docker socket
Mounting /var/run/docker.sock directly into a container is equivalent to giving it root access to the host. If a service like Traefik needs to read Docker state, use a socket proxy like tecnativa/docker-socket-proxy with minimal read-only permissions.
Image scanning
Tools like Trivy let you scan images before deploying them, detecting known vulnerabilities and configuration mistakes in the Dockerfile itself:
trivy image --severity CRITICAL,HIGH mi-app:v1.0
trivy config DockerfileKernel hardening with sysctl
The Linux kernel has configurable parameters that strengthen security at the network and system level. Creating a file at /etc/sysctl.d/99-hardening.conf with at least these settings is good practice:
# Protección contra SYN flood
net.ipv4.tcp_syncookies = 1
# Reverse path filtering (anti IP spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignorar ICMP redirects (prevenir MITM)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# ASLR completo
kernel.randomize_va_space = 2
# Restringir dmesg y punteros del kernel
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
# Restringir ptrace (solo padre-hijo)
kernel.yama.ptrace_scope = 1
# No generar core dumps de procesos SUID
fs.suid_dumpable = 0# Aplicar sin reiniciar
sudo sysctl -p /etc/sysctl.d/99-hardening.confOne important detail: if you use Docker, net.ipv4.ip_forward has to be set to 1. Docker needs it for networking between containers, so make sure you don't disable it.
Filesystem: noexec, nosuid, nodev
Temporary directories like /tmp, /var/tmp, and /dev/shm are common attack vectors. Mounting them with restrictions prevents execution of binaries downloaded by an attacker:
# /etc/fstab
tmpfs /tmp tmpfs defaults,nosuid,noexec,nodev,size=2G 0 0
tmpfs /var/tmp tmpfs defaults,nosuid,noexec,nodev,size=1G 0 0
tmpfs /dev/shm tmpfs defaults,nosuid,noexec,nodev 0 0These options aren't a complete solution (there are in-memory execution techniques that avoid touching disk), but they significantly raise the amount of effort an attacker needs.
HTTP security headers
If you use a reverse proxy like Traefik or Nginx, HTTP headers are another defense layer that costs nothing to add:
- HSTS forces HTTPS in the browser, even if the user types http://. Use
includeSubDomainsandpreloadfor maximum protection. - CSP defines which origins the browser is allowed to load, mitigating XSS attacks and content injection.
- X-Frame-Options prevents clickjacking by stopping your site from being loaded inside an iframe.
- Permissions-Policy restricts access to browser APIs like camera, microphone, or geolocation.
- X-Content-Type-Options: nosniff prevents the browser from interpreting files with a different MIME type than the declared one.
After configuring them, you can validate your score with tools like Security Headers or Mozilla HTTP Observatory. The goal is to reach an A+ rating.
Monitoring and logging
There's no point having all the previous layers if you don't monitor what's happening on your server. A few recommendations:
- With auditd you can log executions as root, changes to critical files (
/etc/passwd,/etc/shadow,sshd_config), and modifications to sudoers. - Configure journald with persistent storage, compression, and retention of at least 90 days.
- The CrowdSec web console gives you visibility into blocking decisions, alerts, and signals shared with the community.
# Consultas útiles con journalctl
journalctl -u sshd --since "1 hour ago" # Logs SSH recientes
journalctl -p err --since today # Solo errores de hoy
journalctl _UID=0 --since today # Acciones de rootReview logs regularly and set up alerts for critical events. A compromised server usually leaves traces before the damage is visible. If opening a terminal every two days sounds impractical, you can also show the aggregated timer status on an e-ink display next to your monitor and check it at a glance.
Security as a process, not a destination
None of these measures on their own turns your VPS into a bunker, but together they create defense in depth that forces an attacker to get through multiple barriers. The idea is that each layer makes up for the weaknesses of the others.
More importantly, security isn't something you configure once and forget. Patches get applied, blocklists get updated, logs get reviewed. It's an ongoing process that needs regular attention.
If you can only do three things today, make it these: switch SSH to key-only auth on a non-standard port, install CrowdSec with a firewall bouncer, and turn on automatic security updates. That alone will already put you ahead of 90% of servers exposed to the internet.
Another entry in the Homelab security series. The next post is Supply chain, Dockerfile, npm, and checksums.

Jose, author of the blog
QA Engineer. I write out loud about automation, AI and software architecture. If something here helped you, write to me and tell me about it.
Leave the first comment
What did you think? What would you add? Every comment sharpens the next post.
If you liked this

Endureciendo ScamDetector contra prompt injection, alucinaciones y abuso
Defensa contra inyección de prompts, prevención de alucinaciones del modelo, rate limiting en capas y el resto de cambios que endurecieron ScamDetector para producción real.

ScamDetector, un detector de estafas con inteligencia artificial
ScamDetector combina inteligencia artificial, búsqueda de reputación de teléfonos y escaneo de URLs para ayudarte a identificar estafas digitales. Sin registro, sin datos almacenados.

Cómo verificamos que nadie manipula los posts de este blog
Nuestros posts viven en una base de datos SQLite. Si alguien accede a ella, puede cambiar cualquier artículo sin dejar rastro. Construimos un verificador externo con hashes SHA-256 y firma Ed25519 que vigila la integridad desde un segundo servidor.