For my experiments I'm renting a remote Linux server. As soon as it was
online, it became clear that the first real problem wasn't installing
software, but reducing how much of the server was exposed to the
internet by default. Services like SSH are continuously scanned and
probed, and a freshly provisioned host is immediately visible.
This lab documents how I secured that host step by step: first by
establishing a strict firewall baseline, then by introducing a private
administrative VPN, and finally by removing public SSH exposure
entirely.
Goal
The goal of this lab is to secure a rented Linux host that acts as the
entry point to a private network.
In this setup, the public host fronts several internal virtual machines.
External HTTP(S) traffic is terminated on the host and routed to
internal services via a reverse proxy, while internal systems are not
directly exposed to the internet.
Although built as a homelab, this mirrors real-world infrastructure
patterns where a single edge node provides controlled access to private
services.
This lab focuses on:
- Reducing the exposed attack surface
- Replacing unrestricted public SSH with controlled access
- Establishing a reusable hardening pattern for future hosts
Constraints / Assumptions
- The host is publicly reachable.
- SSH is initially exposed.
- Out-of-band recovery access is available from the provider.
- A non-root admin user with sudo is used.
- Temporary SSH disruption is acceptable during hardening.
High-Level Design
The host serves two roles:
- Public entry point (reverse proxy, VPN)
- Gateway to a private internal network
Traffic model:
- Internet → Public host (only required ports open)
- Public host → Private VMs
- Administrative access via VPN only (Phase 2)
The final state minimizes public exposure and separates management
traffic from application traffic.
Phase 1 --- Make it work
The objective is to regain control.
Using firewalld with a deny-by-default policy, inbound traffic is
restricted to explicitly allowed services. SSH remains publicly
accessible temporarily to avoid lockout during baseline setup.
Phase 1 ensures:
- Explicitly defined inbound access
- Persistent firewall rules
- Stable administrative connectivity
ICMP remains enabled for diagnostics.
Phase 2 --- Reduce trust / Harden access
With a stable firewall in place, administrative access is redesigned.
Introduce a Private Administrative Network
OpenVPN is deployed to create a private management plane. SSH is no
longer treated as a public service but as a capability granted to
authenticated VPN members.
Split-tunnel mode is intentionally used. Administrative traffic routes
through the VPN, while general internet traffic remains local.
Restrict SSH to VPN + Controlled IP
Once VPN access is validated:
- Public SSH is removed.
- SSH is allowed only from:
- The VPN subnet
- A specific home IP address (fallback)
This eliminates global SSH exposure.
Disable Root SSH Login
Root login is disabled. Administrative access occurs through a dedicated
non-root user with sudo.
Automating Client Profile Generation
Instead of manually assembling the .ovpn file, the repository includes
a helper script:
make-ovpn.sh
The script:
- Reads the server configuration
- Embeds the correct CA and tls-crypt key
- Extracts the correct certificate block
- Generates a ready-to-import profile
Example:
./make-ovpn.sh client1
This prevents formatting mistakes and ensures server/client consistency.
Final Validation Checklist
- SSH not publicly reachable
- SSH reachable via VPN
- Root login disabled
- VPN stable after reboot
- Firewall rules persist
Lessons Learned
- Firewall first, VPN second.
- Separate access plane from data plane.
- tls-crypt keys must match exactly.
- Split-tunnel DNS requires careful handling.
- Root access should be deliberate.
Where to Go Next
- Replace OpenVPN with WireGuard
- Mirror firewall rules at provider level
- Convert the host into a bastion/jump host pattern
- Automate via Ansible
Repository
The full and step‑by‑step documentation — is available here:
👉 https://github.com/ic-devops-lab/devops-labs/tree/main/ProtectRemoteHostWithFirewallAndVPN
This repository is intended to be read alongside the article: the article explains why each layer exists, while the repository shows how it is implemented.


Top comments (0)