Building Your Own Ngrok/Cloudflare Tunnel Alternative Using SSH Reverse Tunneling on a Personal VPS
Introduction: The Cost of Free Tunnels and the Self-Hosted Alternative
For modern software engineers, DevOps professionals, and homelab enthusiasts, exposing a local development server or a private home application to the internet is a daily requirement. Whether you need to test incoming webhooks from platforms like Stripe, showcase a live frontend preview to a stakeholder, or access a web dashboard hidden behind a strict corporate firewall, networking barriers like Carrier-Grade NAT (CGNAT) often stand in your way.
While third-party solutions such as Ngrok and Cloudflare Tunnels have historically filled this gap, modern infrastructure constraints have made them less appealing. Ngrok's free tier has grown increasingly restrictive, enforcing strict bandwidth caps, ephemeral URLs, and mandatory user registrations. Conversely, Cloudflare Tunnels provide stability but force you to route unencrypted traffic directly through their global network edge, presenting significant privacy implications for sensitive data.
The optimal solution for modern businesses and privacy-conscious developers is to eliminate the middleman entirely. By utilizing a low-cost personal Virtual Private Server (VPS), you can construct a resilient, high-performance, and completely sovereign tunneling infrastructure using native SSH Reverse Tunneling combined with Nginx and Let's Encrypt. This guide provides a comprehensive blueprint to deploying your custom tunnel from scratch.
Understanding the Architecture: How SSH Reverse Tunneling Works
In a standard SSH connection, a local client initiates a connection to a remote server to drop into a secure shell environment. An SSH Reverse Tunnel (commonly initiated via the -R flag) reverses this traffic topology. Your local machine, which resides safely inside a private network, establishes an outbound connection to your public-facing VPS. During this process, it instructs the VPS to listen on a designated port and transparently forward all incoming traffic back down that established SSH connection to your local server.
Architectural Pipeline:
Public User Request → Internet → VPS Public IP (Nginx Port 443) → Nginx Upstream Loopback (Port 8080) → SSH Tunnel → Local Machine Service (Port 3000)
By routing the connection this way, you bypass incoming firewall blockages because the local machine is the entity initiating the outbound connection over standard port 22. To transform this raw TCP forward into a production-grade service equivalent to Ngrok, we wrap the VPS ingress point with Nginx as a reverse proxy. This architecture gives us three massive advantages:
- Zero External Dependencies: Total control over data storage, packet inspection, and system timeouts.
- Custom Domain Control: Permanent, branded subdomains without subscribing to enterprise-tier vendor pricing.
- Full Transport Security: End-to-end encryption using automated TLS certificates managed natively on your server.
Step 1: Preparing the VPS for Remote Tunneling
Before launching a reverse tunnel, you must configure your VPS's SSH daemon (sshd) to permit remote loopback interfaces to bind to public network interfaces. Without this adjustment, the forwarded ports will lock down to the server's local loopback (127.0.0.1), rendering them inaccessible to external web browsers.
1. Modify SSH Configuration
Establish a standard SSH connection to your VPS as root or an administrative user with sudo privileges, and open the configuration file:
sudo nano /etc/ssh/sshd_config
Locate the following configuration directives and modify them to match the values below. If they do not exist, append them to the bottom of the file:
GatewayPorts yes
ClientAliveInterval 30
ClientAliveCountMax 3
The GatewayPorts yes directive forces SSH to allow forwarded ports to bind to the wildcard address (0.0.0.0), making them accessible from the public internet. The ClientAlive directives act as a continuous keep-alive mechanism, instructing the server to drop dead connections promptly when a local machine disconnects abruptly, freeing up the port for future sessions.
2. Restart the SSH Service
Apply your updated configurations by restarting the SSH daemon:
sudo systemctl restart sshd
Step 2: Configuring Nginx as a Production-Grade Reverse Proxy
While exposing raw TCP ports directly via SSH works, it lacks standard web protections, forces users to append awkward port numbers to URLs, and leaves connections unencrypted. To solve this, we map incoming traffic on a standard sub-domain directly to our tunnel endpoint using Nginx.
1. Establish DNS Records
Navigate to your DNS registrar and create a new A Record pointing your chosen subdomain to the public IP address of your VPS:
- Type: A
- Name:
tunnel.yourdomain.com(or use a wildcard*.tunnel) - Value:
YOUR_VPS_PUBLIC_IP
2. Install Nginx
Update your system package listings and install Nginx on the VPS platform:
sudo apt update
sudo apt install nginx -y
3. Create the Nginx Site Configuration
Construct a dedicated server block definition to handle incoming web traffic for your tunnel:
sudo nano /etc/nginx/sites-available/tunnel.conf
Populate the file with the following directive structure. In this scenario, we will assume our internal SSH tunnel passes traffic through port 8080 on the server side:
server {
listen 80;
server_name tunnel.yourdomain.com;
location / {
proxy_pass [http://127.0.0.1:8080](http://127.0.0.1:8080);
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Enable WebSocket support for modern web applications
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
4. Activate and Test Nginx Configuration
Link the newly created site configuration to the active directory, verify that the Nginx configuration syntax is clean, and refresh the service:
sudo ln -s /etc/nginx/sites-available/tunnel.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Step 3: Securing the Connection with Let's Encrypt TLS
To mimic the security posture of an enterprise Cloudflare Tunnel, we must handle SSL/TLS termination securely at the Nginx layer. We achieve this quickly using Certbot to deploy free, auto-renewing Let's Encrypt certificates.
1. Install Certbot
Install the Certbot core binaries along with the Nginx automation plugin package:
sudo apt install certbot python3-certbot-nginx -y
2. Generate the SSL Certificate
Execute Certbot targeting your Nginx implementation. The utility automatically scans your site configurations, provisions a valid certificate, and adjusts your server blocks to enforce HTTPS traffic:
sudo certbot --nginx -d tunnel.yourdomain.com
Follow the interactive command-line prompts to complete the deployment. Certbot automatically adds cron jobs to your system to ensure your certificates never expire.
Step 4: Establishing and Automating the Tunnel
With your cloud infrastructure completely established, you are ready to open the reverse tunnel from your local machine. Let us assume you have a local web server running at localhost:3000.
1. The Manual Connection Command
From your local machine terminal, issue the following SSH command to bind your remote VPS port 8080 to your local development port 3000:
ssh -N -R 8080:localhost:3000 user@YOUR_VPS_PUBLIC_IP
Here is a breakdown of the specific flags utilized in this string:
-N: Instructs SSH not to open an interactive terminal shell interface. This is ideal for pure port-forwarding situations.-R 8080:localhost:3000: Maps traffic arriving at port8080of the remote VPS directly down to port3000on your local computer.
Open a web browser and navigate directly to [https://tunnel.yourdomain.com](https://tunnel.yourdomain.com). Your local development framework will display instantly, completely protected by enterprise-grade HTTPS encryption.
2. Making It Bulletproof with AutoSSH
Raw SSH connections frequently drop when changing networks, waking from sleep states, or enduring brief ISP dropouts. To construct a genuinely reliable tunnel that matches the background persistence of the Cloudflare daemon, we can utilize AutoSSH to monitor and revive dropped connections automatically.
First, install the utility on your local system (e.g., via brew install autossh on macOS or sudo apt install autossh on Linux distributions). Next, launch your persistent background session using the following command structure:
autossh -M 0 -f -N -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -R 8080:localhost:3000 user@YOUR_VPS_PUBLIC_IP
The -M 0 flag disables internal loopback monitoring, relying instead on native SSH keep-alive configurations. The -f modifier pushes the execution process directly into the operating system background, keeping your development workflows clean and unhindered.
Security Best Practices and Hardening Your Implementation
Exposing internal development resources to the open web introduces inherent security risks. To protect your internal network and assets, adhere strictly to these critical security protocols:
1. Leverage SSH Key Authentication
Never permit standard password-based administrative access on a public-facing tunneling VPS. Generate strong cryptographic key pairs on your local workspace and enforce public key validation by tweaking /etc/ssh/sshd_config settings to PasswordAuthentication no.
2. Implement a Dedicated Unprivileged Tunnel User
Do not initiate reverse tunnel SSH sessions using the root user profile. Create a system account on the VPS that is explicitly barred from executing system tasks or accessing shell features:
sudo useradd -m -s /usr/sbin/nologin tunneluser
This approach establishes a strict security sandbox. In the unlikely event that your local machine or app framework is compromised, the attacker cannot leverage the open SSH process to execute malicious commands on your host VPS platform.
Conclusion: Ultimate Infrastructure Independence
By leveraging an SSH Reverse Tunnel with an Nginx frontend, you create a robust, production-ready development tunnel that effectively replaces proprietary alternatives. This self-hosted setup bypasses restrictive NAT barriers while maintaining absolute sovereignty over your network traffic, configurations, and data security—all powered by a lightweight, low-cost personal VPS.
