You can test websites locally by typing localhost + port in the browser. But to expose services to the public internet so others can access your URL, you need more configuration. There are two common methods: Nginx and tunneling.

The conclusion: Nginx is better for cloud servers, while services on home mini PCs are better suited for tunneling. Cloud servers usually have public IPs with open ports 443. After configuration, you can directly access via domain name. If your server is at home, HTTP 80 and HTTPS 443 are usually blocked by ISPs to prevent unauthorized commercial website hosting.

One solution is to use a different port instead of 443, then use Nginx to forward to 443. However, accessing services would require yourdomain.com:4433, which feels inelegant. The second solution is using tunneling, with Cloudflared being a common option.

Nginx

Using Ports 80/443

  • First, create a new folder. Let’s assume it’s in ~, so the structure is:

    ~
    β”œβ”€β”€ nginx
    β”‚   └── docker-compose.yml
    └── service1
    β”‚   β”œβ”€β”€ docker-compose.yml
    β”‚   └── volume
    └── service2
        β”œβ”€β”€ docker-compose.yml
        └── volume
    
  • Edit Nginx’s docker-compose.yml. We use Let's Encrypt for automatic HTTPS certificates:

    services:
      nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        restart: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /var/run/docker.sock:/tmp/docker.sock:ro
          - ./certs:/etc/nginx/certs:ro
          - ./vhost.d:/etc/nginx/vhost.d
          - ./html:/usr/share/nginx/html
          - ./conf.d:/etc/nginx/conf.d
        networks:
          - nginx
    
      letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: nginx-letsencrypt
        restart: unless-stopped
        environment:
          - NGINX_PROXY_CONTAINER=nginx-proxy
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock:ro
          - ./certs:/etc/nginx/certs
          - ./vhost.d:/etc/nginx/vhost.d
          - ./html:/usr/share/nginx/html
        depends_on:
          - nginx-proxy
        networks:
          - nginx
    
    networks:
      nginx:
        external: true
    
  • Create a Docker network named nginx:

    docker network create nginx
    
  • Start the container:

    docker compose up -d
    

    Now Nginx and Let’s Encrypt are running. When exposing services (as shown in the Vaultwarden article), you can use Nginx as a reverse proxy.

Using Other Ports

Just change the ports to something else, like:

ports:
  - "8080:80"
  - "8443:443"

Note: Let’s Encrypt still needs port 80 for certificate renewal, which cannot be changed. If these ports are blocked, you can only manually update certificates.

Cloudflared

Cloudflared is Cloudflare’s tunneling client. The basic principle is: install a local client that communicates with Cloudflare’s servers, creating a tunnel. When users visit your site, Cloudflare receives the request and forwards it to the mini PC, enabling public internet access.

With this method, you don’t even need a public IP or open ports. Cloudflare provides basic protection. However, access is limited by Cloudflare, domestic access might be slow, and you need a domain.

Cloudflare Configuration

  • There are multiple ways to configure Cloudflared. I use token mode, where all configuration is in Cloudflare’s cloud and the local client only keeps the token to connect. This way, if you switch devices, you only need to remember the token.

  • Get credentials from Cloudflare. Enable Zero Trust and choose the free plan.

  • Network β†’ Tunnels β†’ Add a tunnel

    Add a tunnel

  • Select Cloudflared as the Tunnel Type

    Cloudflared

  • Follow the setup steps. In the Install section, select Docker and save the Token that appears.

  • Then, add a Public Hostname. This is the hostname for exposing services. Suppose you have an AList service on the mini PC using port 5244. You want alist.example.com to access it:

    Subdomain: alist
    Domain: example.com
    Type: HTTP
    URL: localhost:5244
    

    But if it’s Docker-deployed (recommended), use container names:

    Subdomain: alist
    Domain: example.com
    Type: HTTP
    URL: alist:5244
    
  • Now the cloud setup is done. Let’s configure the mini PC:

Mini PC

  • Create a new folder called cloudflared, placed alongside other services:

    ~
    β”œβ”€β”€ cloudflared
    β”‚   └── docker-compose.yml
    └── service1
    β”‚   β”œβ”€β”€ docker-compose.yml
    β”‚   └── volume
    └── service2
        β”œβ”€β”€ docker-compose.yml
        └── volume
    
  • Create docker-compose.yml. Fill in the token from earlier. You don’t even need to mount local volumes:

    services:
      cloudflared:
        image: cloudflare/cloudflared:latest
        container_name: cloudflared
        restart: unless-stopped
        command: tunnel --no-autoupdate run --token [YOUR TOKEN]
        networks:
          - cloudflared
    
    networks:
      cloudflared:
        external: true
    
  • Create a Docker network named cloudflared:

    docker network create cloudflared
    
  • That’s it! When adding services, just create new domains in Cloudflared.

Other Methods

There are other protocols I won’t try in detail. The principles are similar to the above two:

  • Caddy: Reverse proxy
  • Traefik: Reverse proxy
  • FRP: Tunneling with FRPS on VPS and FRPC on mini PC or router
  • ZeroTire: Tunneling
  • Wireguard: Lightweight VPN, use VPN to connect home network, then access
  • Tailscale: VPN based on Wireguard