VPS Baseline: Install, Harden, and Disable Logs
TL;DR: A VPN node is a small Linux box with SSH, a tunnel daemon, and a kill-switch. Before installing OpenVPN or WireGuard, lock SSH to a non-standard port (e.g., 28422), disable root login, turn fail2ban on, and stop writing access logs to disk.
This article is Part 1 of a 7-part series on building a multi-hop, fail-closed VPS VPN chain with Ansible, LUKS encrypted vaults, Telegram monitoring, and zero-log policy routing.
- Part 1: VPS Baseline: Install, Harden, and Disable Logs
- Part 2: Encrypted Vault for VPN Secrets and Residual Logs
- Part 3: Telegram Alerts Without Leaking the Exit IP
- Part 4: Ansible Playbook for Edge Hop Installation
- Part 5: VPN Chain and End-to-End Encryption Without Persisted Logs
- Part 6: VPN Failover Between VPS Nodes
- Part 7: Operational Tasks: Heal, Add, Remove, and Replace Hops
If a VPS disk is seized, imaged, or recycled by a hosting provider, whatever was written to /var/log walks out with it. A chain that encrypts network traffic but keeps weeks of client IP entries in auth.log is security theatre.
The baseline setup for every node in the chain enforces:
- SSH on a non-default port (
28422), disabling port 22 listeners entirely - Disabling root SSH login (
PermitRootLogin no) - Running
fail2banhooked directly to systemd journald - Firewall packet logging disabled
- Systemd journal stored purely in volatile RAM (
Storage=volatile) rsyslogservice stopped and maskedwtmp,btmp, andlastlogaccounting disabled or linked to/dev/null
Even without disk logging, fail2ban continues to function normally because it reads from the in-memory journal rather than a file on disk.
- A fresh Ubuntu 24.04 or 26.04 VPS node
- Provider web console or a bootstrap SSH session
- An operator
ed25519SSH key added to~/.ssh/authorized_keysfor a dedicated sudo user - Network stability check: verify 10 sequential TCP connection probes to the target port before configuration. Some budget VPS hosts pass ICMP ping but drop TCP under load.
Create a dedicated sudo user and disable password authentication. Do not allow root SSH logins.
Modern Ubuntu releases use systemd socket activation for SSH (ssh.socket). Simply setting Port 28422 in /etc/ssh/sshd_config will be ignored. You must configure the listen port on the socket unit via a drop-in file:
install -d /etc/systemd/system/ssh.socket.d
printf '%s\n' '[Socket]' 'ListenStream=' 'ListenStream=28422' \
> /etc/systemd/system/ssh.socket.d/zz-vpn-port.conf
systemctl daemon-reload
systemctl restart ssh.socket
systemctl restart ssh.service
Confirm that nothing is listening on port 22 and that port 28422 is active:
ss -tlnp | grep -E ':22 |:28422 '
Expected output: :28422 only.
Next, enforce strict SSH rules in /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
AuthenticationMethods publickey
Update your firewall to allow TCP port 28422 and close port 22.
Configure fail2ban to ban IPs after 5 failed authentication attempts using the systemd journal backend so no persistent auth.log is required.
Create /etc/fail2ban/jail.d/sshd-vpn.conf:
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h
port = 28422
Restart fail2ban and test authentication failures from an external test host to confirm auto-banning works as expected.
Eliminate persistent traffic and authentication logs on disk:
# Turn off UFW packet logging
ufw logging off
# Force systemd-journald to operate in volatile RAM only
mkdir -p /etc/systemd/journald.conf.d
cat >/etc/systemd/journald.conf.d/volatile.conf <<'EOF'
[Journal]
Storage=volatile
ForwardToSyslog=no
RuntimeMaxUse=64M
EOF
systemctl restart systemd-journald
# Stop and mask rsyslog
systemctl disable --now rsyslog || true
systemctl mask rsyslog || true
sshd and pam_lastlog still record logins to wtmp, btmp, and lastlog. Link these files to /dev/null or disable pam_lastlog in /etc/pam.d/sshd. After rebooting, running last and lastb should return empty results.
- Volatile in-memory journal (
/run/log/journal), discarded on reboot fail2banactive bans stored in RAM- Telegram alert notifications for live SSH logins (detailed in Part 3)
/var/log/auth.logand/var/log/syslog- Firewall packet connection logs
rsysloglog archives- User login accounting histories
Install essential runtime utilities for tunnel management:
apt-get update && apt-get install -y \
docker.io \
wireguard-tools \
iptables \
nftables \
curl \
jq \
vnstat
Raise nofile ulimits in /etc/security/limits.conf if this VPN node will terminate many concurrent client tunnels. Remove unnecessary daemons; a VPN node should only run SSH, tunnel daemons, and NAT forwarding.
Run this script to verify the baseline:
ss -tlnp | grep ':22 ' && echo FAIL_PORT_22
ss -tlnp | grep ':28422 ' || echo FAIL_SSH_PORT
ufw status verbose | grep -q 'Logging: off' || echo FAIL_UFW_LOG
systemctl is-active rsyslog && echo FAIL_RSYSLOG
findmnt /run/log/journal >/dev/null || echo CHECK_JOURNAL
Trade-off: Disabling disk logs means local forensic history is lost upon reboot. This is intentional. Security alerts are dispatched in real-time via Telegram rather than written locally.
Next in the series: Part 2: Encrypted Vault for VPN Secrets and Residual Logs - Storing WireGuard keys, OpenVPN certificates, and remaining secrets inside a LUKS2 encrypted volume.