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.


Series Navigation: Building a VPS VPN Chain

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.

  1. Part 1: VPS Baseline: Install, Harden, and Disable Logs
  2. Part 2: Encrypted Vault for VPN Secrets and Residual Logs
  3. Part 3: Telegram Alerts Without Leaking the Exit IP
  4. Part 4: Ansible Playbook for Edge Hop Installation
  5. Part 5: VPN Chain and End-to-End Encryption Without Persisted Logs
  6. Part 6: VPN Failover Between VPS Nodes
  7. Part 7: Operational Tasks: Heal, Add, Remove, and Replace Hops

Why the Baseline Matters

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 fail2ban hooked directly to systemd journald
  • Firewall packet logging disabled
  • Systemd journal stored purely in volatile RAM (Storage=volatile)
  • rsyslog service stopped and masked
  • wtmp, btmp, and lastlog accounting 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.

Prerequisites

  • A fresh Ubuntu 24.04 or 26.04 VPS node
  • Provider web console or a bootstrap SSH session
  • An operator ed25519 SSH key added to ~/.ssh/authorized_keys for 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.

Step 1: Operator Account and Non-Standard SSH Port

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.

Step 2: Configure fail2ban

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.

Step 3: Disable Persisted Logs

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.

What is retained:

  • Volatile in-memory journal (/run/log/journal), discarded on reboot
  • fail2ban active bans stored in RAM
  • Telegram alert notifications for live SSH logins (detailed in Part 3)

What is eliminated:

  • /var/log/auth.log and /var/log/syslog
  • Firewall packet connection logs
  • rsyslog log archives
  • User login accounting histories

Step 4: Packages and System Limits

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.

Verification

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.