Reverse Proxy vs. VPN: Choosing the Right Access Method for Self-Hosted Services

Reverse Proxy vs. VPN: Choosing the Right Access Method for Self-Hosted Services

When I first started self-hosting services on my homelab, I made the mistake of thinking there was only one way to access them remotely: set up a VPN tunnel and be done. But after running Nextcloud, Jellyfin, Vaultwarden, and a dozen other services for the better part of a year, I've learned that the choice between a reverse proxy and a VPN isn't black and white—it's architectural, and it depends entirely on what you're trying to do.

I've locked myself out of my own lab, fought with certificate renewal, spent hours debugging port forwarding, and eventually settled on a hybrid approach that works beautifully. In this article, I'll walk you through the real differences, the gotchas, and exactly how I'd set up access today.

What's a Reverse Proxy, Actually?

A reverse proxy sits on the edge of your network (usually on a VPS or your router itself) and forwards incoming traffic to your internal services. When someone visits jellyfin.example.com, the reverse proxy intercepts that request and routes it to your Jellyfin instance running on port 8096 inside your home network.

I prefer Caddy for this because the certificate management is automatic. You point your domain at your reverse proxy's IP, configure a Caddyfile with your services, and it handles Let's Encrypt renewal transparently. Here's what my setup looks like:

jellyfin.example.com {
  reverse_proxy localhost:8096
}

vaultwarden.example.com {
  reverse_proxy localhost:80
}

nextcloud.example.com {
  reverse_proxy localhost:8080
}

The reverse proxy listens on ports 80 and 443 (standard web ports), encrypted automatically with HTTPS, and forwards the connection internally. The beauty is that your internal services don't need to know about TLS or certificate management. They just respond to HTTP on localhost.

What's a VPN, and Why Would You Use One?

A VPN creates an encrypted tunnel from your remote device directly into your home network. Once connected, your laptop or phone is effectively inside the network—able to access services by their internal IP and port, exactly as if you were home.

I use Tailscale for this, and it's genuinely excellent. After installing the client on your devices and authorizing them, you get a private IP address within the Tailscale network. Access to internal services is unrestricted and feels like you're on your home WiFi.

Here's how I run Tailscale in Docker:

docker run -d \
  --name tailscale \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  --cap-add=SYS_MODULE \
  -v /var/lib/tailscale:/var/lib/tailscale \
  -v /dev/net/tun:/dev/net/tun \
  -e TS_AUTHKEY=$AUTHKEY \
  -e TS_STATE_DIR=/var/lib/tailscale \
  tailscale/tailscale:latest

Once running, your machine gets a Tailscale IP (e.g., 100.x.x.x), and you can access any internal service directly. No domain, no public DNS—just internal network semantics.

Tip: If you're using Tailscale, enable subnet routing on your primary machine so all devices on your tailnet can reach your homelab services without running the Tailscale client on every device. This is a lifesaver if you have smart home devices or tablets you can't easily manage.

Key Differences: The Real Trade-offs

Security Model

A reverse proxy exposes your services to the public internet (through standard HTTPS), but each service is individually accessible by its domain name. If someone compromises one service, they've compromised that service. The attack surface is your application code and configuration.

A VPN creates a network tunnel first—authentication happens at the VPN layer. Once you're in, you're trusted. But this means if someone breaks in, they have full network access. I prefer the reverse proxy for individually hardened services and the VPN for accessing network resources like internal dashboards or Prometheus metrics that I don't want exposed at all.

Performance and Latency

Reverse proxies add a single hop: your device → proxy → internal service. On a good connection, this is barely noticeable. VPNs encrypt and route all your traffic through the tunnel, which adds modest overhead but can be slower over high-latency connections (like mobile networks).

For streaming media (Jellyfin, Plex), I use the reverse proxy route because the extra encryption overhead of a VPN can be wasteful. For accessing admin panels and configuration dashboards, VPN is fine.

Ease of Sharing and Collaboration

With a reverse proxy and a public domain, sharing access is simple: give someone the URL and credentials. They connect like they would to any web service. No client installation, no authentication keys, just browser access.

With a VPN, you need to invite devices to your tailnet, distribute authentication keys, and ensure every device runs the client. It's more setup, but it's also more controlled—you see exactly who's connected and when.

Watch out: If you expose services via a reverse proxy on the public internet, make sure you're using strong authentication (not default credentials), enable rate limiting to prevent brute force, and keep your applications updated. A single vulnerable endpoint can compromise your entire infrastructure.

The Hybrid Approach I Actually Use

After a year of experimenting, here's how I've structured access to my homelab:

Public-facing services (reverse proxy): Nextcloud, Vaultwarden, Jellyfin, and my personal blog. These are hardened, run the latest versions, and use strong authentication. They're accessible from anywhere via HTTPS and a domain name. I run these on a small VPS (I use RackNerd for their affordability and reliability—their KVM VPS plans start at just a few dollars and are perfect for reverse proxy workloads).

Internal-only services (VPN): Prometheus, Grafana, Portainer, Home Assistant, and the Tailscale subnet for accessing devices by IP. These require Tailscale to reach. They're never exposed to the public internet and can run with lighter authentication because the network itself is the barrier.

To implement this, I run Caddy on the VPS and Tailscale on my primary homelab server:

#!/bin/bash
# Docker Compose for reverse proxy + metrics access

cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - ACME_AGREE=true
    restart: unless-stopped
    networks:
      - homelab

  tailscale:
    image: tailscale/tailscale:latest
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - SYS_MODULE
    volumes:
      - /var/lib/tailscale:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_AUTHKEY=${TAILSCALE_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
    restart: unless-stopped
    networks:
      - homelab

volumes:
  caddy_data:
  caddy_config:

networks:
  homelab:
    driver: bridge
EOF

docker-compose up -d

The reverse proxy runs on the VPS, Tailscale runs on the homelab itself. Both services are independent—if one fails, the other continues operating. Caddy handles HTTPS and public routing; Tailscale handles private network access.

Which Should You Choose?

Use a reverse proxy if: You want to share services with non-technical users, you're running well-maintained software, you need public accessibility, and you're comfortable with regular security updates. Nextcloud and Vaultwarden are excellent candidates—they're actively developed, handle authentication properly, and benefit from easy sharing.

Use a VPN if: You want to restrict access to trusted devices only, you're accessing administrative interfaces, you want to reach services by internal hostname, or you value simplicity over public shareability. It's also better for accessing multiple services without maintaining separate authentication credentials for each.

Use both if: You're serious about self-hosting. Some services go public, others stay private. It's the most flexible approach and honestly, it's not that much more work once you understand the pieces.

Getting Started Today

If you're setting this up on a VPS, I'd recommend starting with a reverse proxy using Caddy. The setup is straightforward, HTTPS is automatic, and you'll have a public-facing platform in about 10 minutes. For internal services, add Tailscale to your homelab machine—it's a single Docker container and requires minimal configuration.

From there, you can gradually move services into the public-facing tier as you harden them, or keep them all private. The architecture supports both.

Discussion

```