So you don't want to port-forward on your home router or have Cloudflare decrypt all your traffic? Check out Towonel.

Most open source Cloudflare Tunnel alternatives involve setting up a VPS, terminating TLS there on a reverse proxy, then setting up a Wireguard tunnel to your server at home.

Towonel is different: it does not decrypt your traffic on the VPS and you can easily share one, so not every self-hoster has to buy and maintain a VPS.

Check it out!

Mastodon link: https://gts.erwanleboucher.dev/@eleboucher/statuses/01KS4YNA2SYMSP0FSKJVNJA155

all 33 comments

sorted by: hot top controversial new old
[–] 56 points 5 months ago (6 children)

Oh great. So now there's a tuwunel and a towonel and they're completely different things.

(tuwunel is a fork of the matrix backend conduwuit. not to be confused with continuwuity, another conduwuit fork)

  • source
  • hideshow 6 child comments
  • [–] [S] 44 points 5 months ago* (last edited 5 months ago) (2 children)

    I know, the naming isn't ideal.

    On the bright side, you can now expose multiple tuwunel instances via a single towonel and federate with other tuwunels on other towonels for maximum uwu owo

    Which is almost what my friends and I are doing, except we're running continuwuity instead of tuwunel.

  • source
  • parent
  • hideshow 2 child comments
  • [–] 23 points 5 months ago (5 children)

    So I built towonel. In Rust, partly because I wanted to learn the language properly

    This bit makes me a little wary.

  • source
  • hideshow 5 child comments
  • [–] 18 points 5 months ago (4 children)

    Why? I didn’t know python until one of my clients decided they would only use it for everything going forward. It took me all of a day to start converting C# code and this was a decade before LLMs.

    Knowledge of a specific language does not reflect development skill.

  • source
  • parent
  • hideshow 4 child comments
  • [–] 1 point 5 months ago (3 children)

    Yes, but ported C# usually doesn't make for the most idiomatic Python.

    99% of the time that doesn't matter, but a highly security sensitive reverse proxy shared by multiple users most likely part of the stack to be attacked might be an exception.

  • source
  • parent
  • hideshow 3 child comments
  • [–] 2 points 5 months ago (2 children)

    I like how you just assumed that what I was doing wasn’t security oriented…

  • source
  • parent
  • hideshow 2 child comments
  • [–] 11 points 5 months ago

    It's interesting OP. I use the evil Cloudflare Tunnels/Zero Trust, and I'm pretty much sold on it, much to the chagrin of others here. Yes, there are caveats, pros and cons. Even tho I am sold on the product, I would entertain a clone/fork/rewrite if it gave me everything that Cloudflare Tunnels/Zero Trust along with the security features. I'll do some reading once the blog is back up.

  • source
  • [–] 9 points 5 months ago* (last edited 5 months ago)

    Very cool. I personally use a double wireguard network: a wireguard vpn at home for all my services, and then since my home network is behind a double NAT and impossible to access publicly, I use a second wireguard tunnel to a VPS, to forward traffic to my internal wireguard network. The only thing the VPS can see is encrypted wireguard packets.

    Edit: it seems like this service is more for public or shared services (like a public blog), rather than private personal services, so wireguard is less of an option

  • source
  • [–] 7 points 5 months ago (5 children)

    Do most people running a vps reverse proxy terminate tls on the vps? I just proxy TCP 1:1 without touching it to my homelab over my wireguard tunnel. That seems easier than coordinating between the vps which services I'm running locally.

  • source
  • hideshow 5 child comments
  • [–] 2 points 5 months ago (4 children)

    Do you have a link to a tutorial or an example setup for that? I've wanted that exact setup but couldn't find how to do it.

  • source
  • parent
  • hideshow 4 child comments
  • [–] 7 points 5 months ago* (last edited 5 months ago) (1 child)

    Not really haha, you could say I followed a tutorial for setting up a wireguard server on a VPS, and then once I had the wireguard container running and my homelab boxes as clients, I started up an haproxy container on the VPS with network_mode: "service:wireguard" so that the haproxy container can also see my homelab boxes through the tunnel, then also added ports 80 and 443 to the wireguard container on the VPS (in addition to the 51820 for incoming wireguard connections) - that has to be on the wireguard container because using network_mode means the haproxy container piggy backs on the wireguard container's network, then I added a simple haproxy config that listens on 80/443 on the VPSes public IP and proxies it to the appropriate box on the other side of the tunnel.

    For the wireguard config, the key seems to be using mode tcp in any backend or frontend that's connected to port 443, so that it just proxies raw data without doing termination. With SNI, you can even proxy to different wireguard clients based on domain, because SNI exposes the domain without needing to do termination. So I do that because I have my NAS as well as a NUC connected to the wireguard network hosting different things.

    This is a stripped down version of my haproxy config:

    global
        maxconn     20000
        log         127.0.0.1 local0
        daemon
    
    defaults
        mode http
        timeout connect 10s
        timeout client 1m
        timeout server 1m
        maxconn 8000
        option tcpka
        option tcp-smart-connect
        default-server init-addr last,libc,none
    
    resolvers docker
        parse-resolv-conf
    
    frontend ingress_http
        bind :::80
        bind :80
    
        acl h_secondbox_http hdr(host) -i second.box.example.com
        use_backend secondbox_http if h_secondbox_http
    
        default_backend vault_http
    
    frontend ingress_https
        mode tcp
        bind :::443
        bind :443
        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }
    
        acl h_secondbox_https req_ssl_sni -i second.box.example.com
        use_backend secondbox_https if h_secondbox_https
    
        default_backend vault_https
    
    backend vault_http
        server vault_server_http 10.13.13.2:80 send-proxy-v2
    backend vault_https
        mode tcp
        server vault_server_https 10.13.13.2:443 send-proxy-v2
    
    backend secondbox_http
        server secondbox_server_http 10.13.13.3:80 send-proxy-v2
    backend secondbox_https
        mode tcp
        server secondbox_server_https 10.13.13.3:443 send-proxy-v2
    

    The way this is set up, I do have to manually enter every subdomain I want to go to my second box, but the default is to route to my main vault, which is where I host most stuff anyways.

    My docker compose on the VPS is pretty simple:

    services:
      wireguard:
        image: linuxserver/wireguard:latest
        container_name: wireguard
        restart: unless-stopped
        cap_add:
          - NET_ADMIN
          - SYS_MODULE
        environment:
          - PUID=0
          - PGID=0
          - TZ=America/New_York
          - SERVERURL=wg.example.com #optional
          - SERVERPORT=51820 #optional
          - PEERS=vault,secondbox #optional
          - PEERDNS=auto #optional
          - INTERNAL_SUBNET=10.13.13.0 #optional
          - ALLOWEDIPS=10.13.13.1/24 #optional
          - PERSISTENTKEEPALIVE_PEERS=all #optional
          - LOG_CONFS=true #optional
        volumes:
          - ./volumes/wg-config:/config
        ports:
          - 51820:51820/udp
          - 80:80/tcp
          - 443:443/tcp
          - 8090:8090/tcp
        sysctls:
          - net.ipv4.conf.all.src_valid_mark=1
    
      haproxy:
        image: haproxy:lts
        container_name: haproxy
        restart: unless-stopped
        network_mode: "service:wireguard"
        depends_on:
          - wireguard
        volumes:
          - ./volumes/haproxy-config:/usr/local/etc/haproxy
    

    Then on the local side I use the same network_mode: "service:wireguard" trick to link my traefik container to the wireguard container, that way traffic hitting ports 80/443 of the wireguard container which is on the tunnel is also seen by traefik:

    services:
      boringtun:
        image: boringtun
        build: ./boringtun-docker
        container_name: boringtun
        restart: always
        privileged: true
        cap_add:
          - NET_ADMIN
        devices:
          - "/dev/net/tun:/dev/net/tun"
        volumes:
          - "./volumes/wg-config/wg0.conf:/etc/wireguard/wg0.conf"
        logging:
          driver: "json-file"
          options:
            max-size: "400k"
            max-file: "20"
        environment:
          - INTERFACE_NAME=wg0
          - WG_SUDO=1
          - WG_QUICK_USERSPACE_IMPLEMENTATION=/app/boringtun
        entrypoint: /bin/bash
        command: -c "wg-quick up wg0 && sleep infinity"
        extra_hosts: # Allows containers to access the host machine as host.docker.internal, useful for remote access to the host through a container
          - "host.docker.internal:host-gateway"
        networks:
          - ingress
    
      traefik:
        image: traefik:v2.11
        container_name: traefik
        restart: always
        network_mode: "service:boringtun"
        depends_on:
          - boringtun
        command:
          # - "--log.level=DEBUG"
          - "--providers.docker"
          - "--entrypoints.web.address=:80"
          - "--entryPoints.web.proxyProtocol.trustedIPs=10.13.13.1"
          - "--entrypoints.websecure.address=:443"
          - "--entryPoints.websecure.proxyProtocol.trustedIPs=10.13.13.1"
          - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
          - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
          - "--entrypoints.web.http.redirections.entrypoint.priority=100"
          # Timeouts
          - "--entryPoints.websecure.transport.respondingTimeouts.readTimeout=0"
          - "--entryPoints.websecure.transport.respondingTimeouts.writeTimeout=0"
          - "--entryPoints.websecure.transport.respondingTimeouts.idleTimeout=0"
          - "--providers.docker.exposedByDefault=false"
          - "--providers.docker.network=ingress"
          - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
          - "--certificatesresolvers.mytlschallenge.acme.email=youremail@example.com"
          - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
          - "--serversTransport.forwardingTimeouts.dialTimeout=3m"
          # - "--api.insecure=true"
          # - "--certificatesresolvers.mytlschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
        environment:
          - TZ=America/New_York
        volumes:
          - ./volumes/le-data/acme.json:/letsencrypt/acme.json
          - /var/run/docker.sock:/var/run/docker.sock
    

    I only use boringtun on this side because I think synology doesn't or didn't have the kernel module for wireguard and using the userspace mode made it work for me, otherwise you could probably just use the regular wireguard container. Also note that my docker network for communicating between traefik and stuff I'm exposing is ingress, which is specified both on the boringtun container as well as passed to traefik as providers.docker.network, I think that's needed so that traefik can figure out the container IP of the containers you're exposing. I also haven't migrated to traefik v3 because I'm lazy.

    Another note, there's an annoying condition where if you reboot, it may fail to attach the traefik container to wireguard because it linked via network mode to the old container. Just doing compose down and up fixes it by recreating all the containers. But other than that which I haven't encountered in a while it works really well. I'm not sure if that bug was fixed because I rarely reboot.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 2 points 5 months ago (1 child)

    Not exactly a tutorial, but I use SNI routing + TLS passthrough with Caddy-L4 (and previously Traefik), and wrote/collect some stuff about it over the years:

    {
        layer4 {
            tcp/:443 {
                tcp/127.0.0.1:538
            }
        }
    }
    
  • source
  • parent
  • hideshow 1 child comment
  • [–] 6 points 5 months ago (1 child)

    Half of Cloudflare's value proposition is the network that provides these services. It's well connected and will have good performance in most places around the world. The software on top is the "easy" part. I'm glad to see other options though. Nice work

  • source
  • hideshow 1 child comment
  • [–] [S] 2 points 5 months ago*

    Haha don't worry, Erwan is already thinking about high availability and edge nodes closer to clients!

    Maybe a Cloudflare competitor like Bunny CDN could use this to build their own tunnel service?

  • source
  • parent
  • [–] 5 points 5 months ago

    Oh, nice find. I'm saving that

  • source
  • [–] 5 points 5 months ago* (1 child)

    Uh. Blog is down. All I get is an 404 for the link in the Mastodon post.

    Edit: Here's a link that works: https://github.com/eleboucher/towonel

  • source
  • hideshow 1 child comment
  • [–] 4 points 5 months ago (1 child)

    Isn't this similar to rathole or frp?

  • source
  • hideshow 1 child comment
  • [–] [S] 5 points 5 months ago*

    Very similar.

    The main differences are that those projects are highly configurable and can do a lot of things, while towonel is simpler: opinionated/streamlined for use as a shared Cloudflare tunnel alternative. I also think towonel may be the only one to use QUIC for the tunnel, just like Cloudflare.

    Besides that, towonel is very new and still in alpha. Rathole does not seem to be actively developed anymore, which can be a good or bad thing.

  • source
  • parent
  • [–] 3 points 5 months ago (3 children)

    Is the agent only available as a docker image? I quite like the option to run Cloudflare tunnels as a local service (e.g. in LXCs).

  • source
  • hideshow 3 child comments
  • [–] [S] 1 point 5 months ago (2 children)

    I'm sorry, I'm not that familiar with LXC.

    Erwan will make binaries available ASAP, would that help you?

  • source
  • parent
  • hideshow 2 child comments
  • [–] [B] 6 points 5 months ago* (last edited 5 months ago)

    Acronyms, initialisms, abbreviations, contractions, and other phrases which expand to something larger, that I've seen in this thread:

    Fewer Letters More Letters
    IP Internet Protocol
    LXC Linux Containers
    NAS Network-Attached Storage
    NAT Network Address Translation
    NUC Next Unit of Computing brand of Intel small computers
    SSL Secure Sockets Layer, for transparent encryption
    TCP Transmission Control Protocol, most often over IP
    TLS Transport Layer Security, supersedes SSL
    VPS Virtual Private Server (opposed to shared hosting)

    8 acronyms in this thread; the most compressed thread commented on today has 15 acronyms.

    [Thread #304 for this comm, first seen 21st May 2026, 13:30] [FAQ] [Full list] [Contact] [Source code]

  • source
  • 1 comment in c/selfhosting@slrpnk.net view that post
    [–] [S] 4 points 5 months ago

    For those who block Lemmy.world :


    Towonel: Open Source drop-in Cloudflare Tunnel alternative

    So you don't want to port-forward on your home router or have Cloudflare decrypt all your traffic? Check out Towonel.

    Most open source Cloudflare Tunnel alternatives involve setting up a VPS, terminating TLS there on a reverse proxy, then setting up a Wireguard tunnel to your server at home.

    Towonel is different: it does not decrypt your traffic on the VPS and you can easily share one, so not every self-hoster has to buy and maintain a VPS.

    Check it out!

    Mastodon link: https://gts.erwanleboucher.dev/@eleboucher/statuses/01KS4YNA2SYMSP0FSKJVNJA155

  • source