How to Secure a Linux VPS or Root Server on Netcup (5-Layer Defense-in-Depth Blueprint)
Relying on a single security control (such as UFW alone) leaves a production server brittle: a single docker run -p command can bypass UFW, and a single unpatched OpenSSL library in memory remains vulnerable even after apt upgrade unless the daemon is restarted. This guide implements a 5-layer defense-in-depth architecture specifically engineered for Netcup KVM servers across the Generation 11.5s and Generation 12.5 lineup.
systemd journal backend for Fail2ban, needrestart, and unattended-upgrades) and Netcup’s hypervisor-edge SCP Stateful Firewall. PREREQUISITES // ENVIRONMENT REQUIREMENTS
CHECKLIST- ✓ A Netcup VPS or Root Server running Ubuntu 24.04 LTS or Debian 12
- ✓ Ed25519 SSH key authentication already configured and tested
Key Takeaways
EXECUTIVE BRIEFING-
01 Layer 1 (Hypervisor Edge): Configure the Netcup SCP Stateful Firewall to drop unauthorized TCP/UDP ports outside the VM before packets hit your guest kernel or Docker
PREROUTINGchains. -
02 Layer 2 (Host Firewall): Enforce dual-stack UFW (
IPV6=yes) inside the guest OS. -
03 Layer 3 (Intrusion Prevention): Configure Fail2ban with
backend = systemd(mandatory on Ubuntu 24.04 where/var/log/auth.logis absent) and exponential ban multipliers (bantime.increment = true). -
04 Layer 4 & 5 (Kernel Hardening & Automated CVE Patching): Lock down
/etc/sysctl.d/99-netcup-security.confand configureunattended-upgrades+needrestartfor automatic service restarts after library updates.
📑 Quick Jump Table of Contents (4 Sections) ▼
- 01 Layer 1: Configure the Netcup SCP Hypervisor-Edge Stateful Firewall
- 02 Layer 2: Deploy Fail2ban with backend = systemd & Exponential Ban Multipliers
- 03 Layer 3: Kernel Network & Memory Exploit Mitigation (/etc/sysctl.d/)
- 04 Layer 4: Automated Security Patching (unattended-upgrades) & Daemon Reloading (needrestart)
Layer 1: Configure the Netcup SCP Hypervisor-Edge Stateful Firewall
Introduced across Generation 11, 11.5s, 12, and 12.5 in the Netcup Server Control Panel (servercontrolpanel.de), the SCP Stateful Firewall filters traffic at the host hypervisor virtual switch (tap interface) before packets ever enter your virtual machine’s virtio-net queue. This architecture provides two decisive operational advantages over relying solely on guest-OS iptables or ufw: first, volumetric TCP SYN or UDP port scans are dropped on the host bridge without consuming your VM’s vCPU softirq interrupt budget or conntrack table; second, because the filter sits outside your guest kernel, a misconfigured Docker 0.0.0.0:5432 port binding inside the VM cannot accidentally expose a database to the public internet.
Because the SCP Firewall is stateful (connection-tracking aware), an Egress: ACCEPT ALL rule automatically permits return packets (ESTABLISHED,RELATED) for any outbound DNS queries, apt repository downloads, or HTTPS API calls initiated by your server, while a default Ingress: DROP ALL policy at the bottom of the ruleset blocks all unsolicited inbound connections not explicitly whitelisted above it. You can also save your ruleset as a reusable Firewall Policy Template inside SCP and attach it across multiple Netcup VPS and Root Server instances in a single click or via the SCP REST API.
One critical IPv6 rule must never be overlooked when building an SCP Firewall policy: never block inbound ICMPv6 completely. Unlike IPv4, which uses Layer-2 ARP broadcasts, IPv6 relies entirely on ICMPv6 Neighbor Discovery Protocol (NDP, types 133–136) to resolve your link-local gateway (fe80::1) and relies on ICMPv6 Packet Too Big (type 2) for Path MTU Discovery. Blocking ICMPv6 in the SCP Firewall will cause your server’s IPv6 connectivity to drop as soon as the kernel neighbor cache expires. Configure the production ingress/egress policy below under SCP → Select Server → Firewall:
| Direction | Protocol | Source IP / CIDR | Destination Port | Action & Engineering Purpose |
|---|---|---|---|---|
| Ingress (In) | TCP | 0.0.0.0/0, ::/0 (or Admin IP) | 22 (or Custom SSH) | ACCEPT — OpenSSH remote administration |
| Ingress (In) | TCP | 0.0.0.0/0, ::/0 | 80, 443 | ACCEPT — HTTP/1.1, HTTP/2 TLS web traffic |
| Ingress (In) | UDP | 0.0.0.0/0, ::/0 | 443 | ACCEPT — HTTP/3 (QUIC) UDP transport |
| Ingress (In) | ICMP / ICMPv6 | 0.0.0.0/0, ::/0 | All (Esp. Types 133–136) | ACCEPT — Path MTU Discovery & IPv6 Neighbor Discovery |
| Ingress (In) | ALL | 0.0.0.0/0, ::/0 | 1 – 65535 | DROP (Default Policy) — Blocks all internal DB/Docker ports |
| Egress (Out) | ALL | 0.0.0.0/0, ::/0 | 1 – 65535 | ACCEPT — Outbound package updates, API calls & DNS |
Layer 2: Deploy Fail2ban with backend = systemd & Exponential Ban Multipliers
While the SCP Firewall restricts which ports are reachable, ports 22 (SSH) and 443 (HTTPS) must remain open to serve legitimate administrators and visitors. That makes host-level intrusion prevention essential. On Ubuntu 24.04 LTS and modern Debian 12 minimal cloud images, the legacy rsyslog daemon is no longer installed by default—meaning /var/log/auth.log and /var/log/syslog do not exist on disk! If you install fail2ban using old tutorials that rely on the auto or pyinotify file backends, the sshd jail will either crash on startup (Have not found any log file for sshd jail) or silently fail to detect attacks.
To integrate Fail2ban cleanly with systemd-journald on Ubuntu 24.04, always install the python3-systemd C-binding package alongside fail2ban and explicitly configure backend = systemd inside /etc/fail2ban/jail.local. Instead of polling text files, Fail2ban subscribes directly to structured kernel/daemon journal events (_SYSTEMD_UNIT=ssh.service). Setting mode = aggressive inside the [sshd] jail instructs Fail2ban’s regex filter to catch not only failed password or publickey attempts, but also pre-authentication scanner disconnects (Connection closed by authenticating user ... [preauth]) and banner-probing bots that waste sshd unprivileged child processes.
Rather than using a static 10-minute ban—which distributed botnets trivially wait out before resuming their dictionary sequence—enable exponential ban scaling (bantime.increment = true, bantime.factor = 2, bantime.maxtime = 5w). With this configuration, an attacking IP receives a 1-hour ban on its first offense, a 2-hour ban on its second, 4 hours on its third, and scales exponentially up to 5 weeks while Fail2ban persists the offender history across reboots in /var/lib/fail2ban/fail2ban.sqlite3:
sudo apt update
sudo apt install -y fail2ban python3-systemd
cat << 'EOF' | sudo tee /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 5w
findtime = 15m
maxretry = 3
backend = systemd
banaction = ufw
banaction_allports = ufw
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
mode = aggressive
port = ssh
logpath = %(sshd_log)s
backend = systemd
EOF
sudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd Command Explanation: Installs python3-systemd so Fail2ban can query journald directly, integrates ban actions with ufw, enables mode = aggressive (which catches pre-auth scanner disconnects before a password/key attempt even completes), and activates exponential ban scaling.
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| `- Total failed: 0
`- Actions
|- Currently banned: 0
`- Total banned: 0 On Ubuntu 24.04 LTS, always install python3-systemd alongside fail2ban so the systemd backend can subscribe to structured _SYSTEMD_UNIT=ssh.service journal events in real time.
Layer 4: Automated Security Patching (unattended-upgrades) & Daemon Reloading (needrestart)
Understanding how Linux manages shared libraries in virtual memory reveals a subtle security pitfall that catches many sysadmins off guard. When unattended-upgrades downloads and installs an urgent security update for libssl3, libc6, or libsystemd0 at 04:00 UTC, dpkg atomically replaces the .so shared library file on the ext4 filesystem and unlinks the old inode. However, any long-running daemon that was already executing (nginx, php-fpm, postfix, mariadbd) keeps the old, unpatched shared library mapped in its virtual address space (/proc/<pid>/maps showing (deleted)) until that daemon process is restarted!
Ubuntu 24.04 LTS includes the needrestart utility, which hooks into apt / dpkg post-invoke triggers, scans /proc/*/maps across all running processes for unlinked shared library mappings, and identifies which systemd service units need to be restarted. By default, however, needrestart can pause in interactive mode (i) or skip certain restarts during non-interactive runs unless explicitly configured.
By creating /etc/needrestart/conf.d/99-netcup-auto.conf with $nrconf{restart} = 'a';, you instruct needrestart to automatically restart affected userland daemons (nginx.service, php8.3-fpm.service, cron.service) immediately after unattended-upgrades patches a shared library, while safely deferring critical display/session managers (dbus, systemd-logind) and kernel reboots. For kernel updates, we recommend keeping Unattended-Upgrade::Automatic-Reboot "false"; on single-node stateful servers and monitoring /var/run/reboot-required so you can take a quick SCP snapshot before rebooting during a planned maintenance window:
sudo apt install -y unattended-upgrades needrestart
# Configure needrestart to automatically restart daemons when shared libraries are patched
sudo mkdir -p /etc/needrestart/conf.d
cat << 'EOF' | sudo tee /etc/needrestart/conf.d/99-netcup-auto.conf
# Restart services automatically ('a') instead of interactive prompt ('i')
$nrconf{restart} = 'a';
EOF
# Verify unattended-upgrades dry-run
sudo unattended-upgrade --dry-run --debug | tail -n 10 Command Explanation: Ensures that whenever unattended-upgrades patches libssl or glibc, dependent services (nginx, ssh, postfix) are automatically restarted with the patched library.
Common Troubleshooting Scenarios
DIAGNOSTIC RUNBOOK [SYMPTOM] Fail2ban fails to start on Ubuntu 24.04 with Failed during configuration: Have not found any log file for sshd jail
Root Cause: Ubuntu 24.04 removed rsyslog and /var/log/auth.log in favor of systemd-journald.
Resolution: Install python3-systemd (sudo apt install -y python3-systemd) and set backend = systemd in /etc/fail2ban/jail.local as shown in Layer 2.
[SYMPTOM] How do I unban a developer IP address that accidentally triggered Fail2ban?
Root Cause: Three failed SSH key or port attempts within 15 minutes triggered the sshd jail.
Resolution: Run sudo fail2ban-client set sshd unbanip 198.51.100.42 and add trusted static office/VPN IPs to ignoreip in /etc/fail2ban/jail.local.
[SYMPTOM] Locked out of server after applying an overly strict SCP Hypervisor Firewall rule
Root Cause: Port 22/tcp (or your custom SSH port) was omitted before setting the default SCP Ingress policy to DROP.
Resolution: Log in to servercontrolpanel.de (SCP), open the Firewall tab, add the ACCEPT rule for TCP port 22, and click Apply Rules—changes take effect on the hypervisor virtual switch within 2 seconds without rebooting the VM.
Frequently Asked Questions
VERIFIED Q&AIf I use the Netcup SCP Stateful Firewall, do I still need UFW inside Ubuntu?
Yes. Defense-in-depth means never relying on a single control plane: the SCP Stateful Firewall protects the external hypervisor interface (including against Docker port bypasses), while UFW + Fail2ban dynamically block abusive IPs at the guest OS layer.
Should I install CrowdSec or Fail2ban on a Netcup VPS?
Both work well. Fail2ban (~25 MB RAM footprint) is ideal on compact 2 GB to 4 GB RAM instances (such as the VPS nano G11.5s or VPS 500 G12.5) if you want a zero-dependency local daemon with instant systemd journal parsing. CrowdSec (~120 MB RAM footprint) is a great fit on 8 GB+ VPS 1000 G12.5 or RS 1000 G12.5 servers if you want collaborative community blocklists and HTTP WAF bouncers.
Does the Netcup SCP Stateful Firewall cost extra on VPS or Root Server plans?
No! The SCP Stateful Firewall is included free of charge across all current Netcup VPS (G11.5s / G12.5) and Root Server (G12.5) plans, and supports reusable firewall policy templates across multiple servers.
Should I enable automatic reboots for kernel updates in unattended-upgrades?
For single-node production databases, we recommend leaving Unattended-Upgrade::Automatic-Reboot "false"; so needrestart restarts userland daemons automatically while allowing you to schedule a 30-second maintenance window (after taking an SCP snapshot) when /var/run/reboot-required appears.
REFERENCES // VERIFIED TECHNICAL SOURCES
- [1] Ubuntu Server Security & Unattended Upgrades Documentation (https://ubuntu.com/server/docs/security-introduction)
- [2] Netcup Helpcenter — SCP Stateful Firewall (https://helpcenter.netcup.com/)