How to Expose Your Home Server to the Surface Web (Without Opening Ports or Using Tor)
If you've asked "can I use Tor to publish my home server as a regular .com website," you've already found the right problem — you want your home server reachable from the internet, without exposing your home IP, without dealing with your ISP's NAT or CGNAT, and without paying for a VPS.
The answer isn't Tor. Tor exit nodes are for anonymous browsing, not for hosting. What you actually want is a tunnel — a persistent outbound connection from your server to a public endpoint, which forwards traffic back in. Your server initiates the connection, so no inbound ports need to be open, and your home IP stays hidden.
This article covers four approaches, from the simplest to the most controlled, with real configuration examples for each.
The core problem: why your home server isn't reachable
Before picking a solution, it helps to understand exactly what's blocking you.
NAT (Network Address Translation) — your router has one public IP, and all devices on your home network share it. Incoming connections have no way to know which internal device to reach unless you manually configure port forwarding.
CGNAT (Carrier-Grade NAT) — many ISPs, especially on mobile and some residential plans, put you behind a second layer of NAT at the ISP level. Even if you configure port forwarding on your router, you still don't have a routable public IP. There's nothing to forward to.
Dynamic IP — even if you have a real public IP, it changes. You can work around this with dynamic DNS (DDNS), but it's another moving part.
Port 80/443 blocking — some ISPs block inbound connections on ports 80 and 443 for residential accounts. You can change the port, but then visitors need to type yoursite.com:8080, which isn't practical.
Tunnels solve all of these simultaneously. Your server makes an outbound connection to a relay node. Traffic destined for your domain hits the relay, gets forwarded through the tunnel to your server, your server responds, and the response goes back the same way. No inbound ports. No public IP requirement. No CGNAT problem.
Option 1: Cloudflare Tunnel (easiest, free)
Cloudflare Tunnel (formerly Argo Tunnel) is the lowest-friction option. It's free, requires no open ports, and handles HTTPS automatically.
Requirements: A domain whose DNS is managed by Cloudflare (free plan works).
Install cloudflared on your server:
# Debian/Ubuntu
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb
# Or via package manager
wget -q https://pkg.cloudflare.com/cloudflare-main.gpg -O /usr/share/keyrings/cloudflare-main.gpg
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' \
| sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
Authenticate and create a tunnel:
cloudflared tunnel login
# Opens browser for Cloudflare auth
cloudflared tunnel create my-home-server
# Creates tunnel, outputs a UUID like: a1b2c3d4-...
cloudflared tunnel route dns my-home-server yoursite.com
# Creates CNAME record in Cloudflare DNS
Configure the tunnel (~/.cloudflared/config.yml):
tunnel: a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /home/user/.cloudflared/a1b2c3d4-xxxx.json
ingress:
- hostname: yoursite.com
service: http://localhost:80
- hostname: www.yoursite.com
service: http://localhost:80
- service: http_status:404
Run as a system service:
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
Your site is now live at yoursite.com. Cloudflare handles TLS termination, so your local server can run plain HTTP on port 80 internally while visitors get HTTPS externally.
Trade-offs: Cloudflare sees all your traffic (they're the relay). For most personal projects this is fine. For privacy-sensitive deployments, see options 3 and 4.
Option 2: Nginx + a lightweight VPS as relay
If you want more control — or if Cloudflare's terms don't suit your use case — a small VPS acting as a relay is the next step. You run an SSH tunnel or WireGuard between your VPS and home server, then use Nginx on the VPS to proxy traffic to your home server.
The architecture:
Visitor → VPS (yoursite.com, public IP) → SSH/WireGuard tunnel → Home server (nginx/apache)
On the VPS — set up a persistent reverse SSH tunnel:
Your home server initiates an outbound SSH connection to the VPS, forwarding a port:
# On home server — forward local port 8080 to VPS port 9000
ssh -N -R 9000:localhost:8080 user@your-vps-ip
# Make it persistent with autossh
sudo apt install autossh
autossh -M 0 -N -R 9000:localhost:8080 user@your-vps-ip \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3"
Systemd service for autossh (/etc/systemd/system/tunnel.service):
[Unit]
Description=Reverse SSH tunnel to VPS
After=network.target
[Service]
User=youruser
ExecStart=/usr/bin/autossh -M 0 -N \
-R 9000:localhost:8080 user@your-vps-ip \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-i /home/youruser/.ssh/id_rsa
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl enable tunnel
sudo systemctl start tunnel
On the VPS — Nginx reverse proxy:
server {
listen 80;
server_name yoursite.com www.yoursite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yoursite.com www.yoursite.com;
ssl_certificate /etc/letsencrypt/live/yoursite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yoursite.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9000;
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;
}
}
Get a free TLS cert on the VPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yoursite.com -d www.yoursite.com
For the VPS itself, you want something minimal — the relay only needs to forward traffic, not run your application. A 1-core/512MB instance is sufficient. vpso.cc has VPS plans that work for this role; the key requirement is a static public IP and reliable uptime, which is standard across most providers.
Option 3: WireGuard tunnel (faster, more private)
SSH tunneling works but has overhead. WireGuard is a modern VPN protocol that's faster, more efficient, and cleaner to configure. Same architecture as Option 2, but the tunnel is WireGuard instead of SSH.
On the VPS — install and configure WireGuard:
sudo apt install wireguard
# Generate keys on VPS
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
VPS WireGuard config (/etc/wireguard/wg0.conf):
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <VPS_PRIVATE_KEY>
[Peer]
PublicKey = <HOME_SERVER_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
Home server WireGuard config (/etc/wireguard/wg0.conf):
[Interface]
Address = 10.0.0.2/24
PrivateKey = <HOME_SERVER_PRIVATE_KEY>
[Peer]
PublicKey = <VPS_PUBLIC_KEY>
Endpoint = your-vps-ip:51820
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25
# Enable on both machines
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
Then update your Nginx config on the VPS to proxy to 10.0.0.2:80 (your home server's WireGuard IP) instead of localhost:9000. Traffic flows through the encrypted WireGuard tunnel rather than an SSH connection.
Option 4: ngrok (easiest for testing, not production)
ngrok is useful for development and testing — exposing a local server quickly without any configuration. Not recommended for production because the free tier gives you a random subdomain that changes on restart, and paid tiers are relatively expensive for persistent use.
# Install
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt update && sudo apt install ngrok
# Authenticate
ngrok config add-authtoken YOUR_TOKEN
# Expose local port 80
ngrok http 80
You get a public HTTPS URL immediately. Good for showing someone a local project, not for running a real site.
Choosing the right option
| Cost | Control | Privacy | Setup complexity | |
|---|---|---|---|---|
| Cloudflare Tunnel | Free | Low (CF sees traffic) | Cloudflare-level | Low |
| SSH reverse tunnel + VPS | VPS cost | Full | Good | Medium |
| WireGuard + VPS | VPS cost | Full | Best | Medium |
| ngrok | Free/paid | Low | Moderate | Very low |
For most home server setups: Cloudflare Tunnel if you don't mind Cloudflare seeing your traffic and you want zero-maintenance. WireGuard + VPS if you want full control and better privacy — the VPS cost is typically $3–6/month for a relay-only instance.
What about Tor?
Since this comes up: you can run a Tor hidden service and also expose it to the surface web via a Tor2Web proxy, but this is not recommended for a production site. The latency is significant (300ms+ added per hop), Tor2Web proxies are run by third parties you don't control, and the setup gives you the downsides of both systems without the full benefits of either.
The tunnel approaches above are faster, more reliable, and easier to maintain. Tor makes sense for anonymity-critical deployments. For a regular home-hosted website, it's the wrong layer to solve this at.
Security considerations
A few things worth addressing before you go live:
Rate limiting on the VPS (Nginx):
# Add to nginx.conf http block
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
# Add to server block
limit_req zone=one burst=20 nodelay;
Fail2ban on the VPS to block repeated bad requests:
sudo apt install fail2ban
sudo systemctl enable fail2ban
Keep your home server's SSH port closed to the VPS inbound — the tunnel is initiated from your home server outbound, so no inbound SSH from the VPS to home is needed. On your VPS, allow only ports 80, 443, and the WireGuard port (51820):
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 51820/udp
sudo ufw enable
Your home IP remains hidden as long as your application doesn't leak it (check headers, error pages, and any user-generated content that might reference internal IPs).
Top comments (1)
For the VPS options, worth noting the relay IP is datacenter space, and plenty of reputation feeds flag entire hosting ranges as proxy by default. Grading the /24 tells you more than checking the single address, since a block where 30% of neighbours are already flagged drags yours down with it. Cheap to check before you point DNS at it.