Documentation
API reference

Start here

Search every guideesc close

Connections

Use your own tunnel or domain

Reach your server at full quality through Tailscale, a Cloudflare Tunnel, or your own reverse proxy, with no port forwarding.

7 min read

If your connection can't accept incoming connections, or you simply prefer not to open a port, you can point tofa at an address you already control. Apps then connect through it at full quality, with none of the relay's bandwidth caps.

This is one line of configuration on the tofa side. Add the address under Admin, Settings, Remote Access, Custom access URLs, and we advertise it to your apps as another direct path. Everything else on this page is about setting up that address.

This adds a path, it does not replace one

Your apps race every path they know about and use whichever answers first. A tunnel sits alongside local playback, port forwarding, and the relay. If the tunnel is down, apps can try another reachable path.

What the address has to look like#

We validate the URL when you add it, so it is worth knowing the rules up front. The address must:

  • use https://, with a certificate a normal browser trusts. Plain HTTP is rejected, because secure clients cannot reach it.
  • point at the server's root. https://tofa.example.com is fine, https://example.com/tofa is not. tofa does not serve under a sub-path.
  • carry no query string, fragment, or embedded username and password.
  • not sit behind a second login. Cloudflare Access, an authentication proxy, or HTTP basic auth in front of the server will block both our reachability check and every app. tofa does its own authentication; that is what the address is for.
  • pass WebSocket connections through. Apps hold a live connection for playback state and library updates.

A port is allowed if you need one, for example https://tofa.example.com:8443. You can add up to three addresses, which is useful if you run both a tunnel and your own domain.

Set it from your deployment instead#

If you deploy with Docker Compose, Ansible, or a template such as Saltbox, you can set the addresses in the container's environment rather than in Settings:

environment:
  TOFA_CUSTOM_ACCESS_URLS: "https://tofa.example.com,https://tofa.tail1234.ts.net"

Separate addresses with commas. The same rules apply, and anything that breaks them is skipped with a warning in the server log rather than stopping the server. Addresses from the environment show in Remote Access marked From environment. If you add an address in Settings, your saved list replaces the environment ones entirely.

Tailscale#

The quickest option if the only people watching are you and your own devices. Tailscale gives your server a name and a real certificate on your private network, and nothing is exposed to the public internet.

Usually there is nothing to configure

If Tailscale is installed on the machine running tofa, and tofa can see it (running directly on the machine, or in a container on the host network), we find the tailnet address on our own and advertise it for you. Your apps pick it up on the next check-in, and Remote Access shows it as a private network path. You do not need the tailscale serve step or a custom access URL.

The one case that still needs the manual steps below is a container on a bridge network, which cannot see the host's tailnet address at all.

  1. Install Tailscale on the machine running tofa, and on every device you want to watch on. There are apps for iPhone, iPad, Apple TV, Android, and Android TV. There is no Tailscale app for webOS or Tizen televisions, so the built-in TV apps cannot use this path.

  2. In the Tailscale admin console, turn on MagicDNS and HTTPS Certificates.

  3. On the server, publish tofa on your tailnet:

    tailscale serve --bg --https=443 http://localhost:33333

    Run tailscale serve status to see the resulting address. It looks like https://your-machine.your-tailnet.ts.net.

  4. Add that address under Custom access URLs.

Only tailnet devices can use it

People you share your server with are not on your tailnet, so they keep using the relay unless you invite them to it. If you want a path that works for everyone, use a tunnel or your own domain instead.

Tailscale Funnel makes a tailnet service public, but it routes your traffic through Tailscale's own infrastructure and is not built for sustained video. We do not recommend it for streaming.

Cloudflare Tunnel#

A public hostname that works for everyone you share with, and needs no open port. Read the note at the end of this section before committing to it.

  1. Install cloudflared on the machine running tofa and sign in:

    cloudflared tunnel login
    cloudflared tunnel create tofa
  2. Point a hostname at the server's root. In your cloudflared config:

    tunnel: <your-tunnel-id>
    credentials-file: /root/.cloudflared/<your-tunnel-id>.json
    ingress:
      - hostname: tofa.example.com
        service: http://localhost:33333   # no path, the server's root
      - service: http_status:404
  3. Create the DNS record and run the tunnel as a service:

    cloudflared tunnel route dns tofa tofa.example.com
    cloudflared service install
  4. Leave WebSocket support on, and do not put a Cloudflare Access policy in front of the hostname.

  5. Add https://tofa.example.com under Custom access URLs.

Cloudflare's terms limit video through their network

Cloudflare restricts video delivery through its CDN unless you use an eligible paid service or plan. Check its video delivery policy before relying on a tunnel for streaming. A tunnel working today does not establish that your use is allowed by the plan.

Your own reverse proxy#

If you already run Caddy, nginx, Traefik, or a bundle like SWAG, point a hostname at the server and add it. With Caddy the whole configuration is:

tofa.example.com {
    reverse_proxy localhost:33333
}

Caddy handles the certificate, WebSocket upgrades, and streaming responses on its own. With nginx, three settings matter and are easy to miss:

location / {
    proxy_pass http://127.0.0.1:33333;

    # WebSockets: live playback state and library updates
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;

    # Video must stream through, not be collected first
    proxy_buffering off;
    proxy_read_timeout 600s;
}

Buffering is the one that bites: with it on, nginx tries to collect each video segment before passing it along, and playback stalls or starts slowly.

SWAG#

SWAG does not ship a preset for tofa yet, so here is one. Save it as /config/nginx/proxy-confs/tofa.subdomain.conf in your SWAG config folder and restart SWAG:

## Version 2026/10/19
# make sure that your tofa container is named tofa
# make sure that your dns has a cname set for tofa
# if tofa is running in bridge mode and the container is named "tofa", the below config should work as is
# if not, replace the line "set $upstream_app tofa;" with "set $upstream_app <containername>;"
# or "set $upstream_app <HOSTIP>;" for host mode, HOSTIP being the IP address of tofa

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name tofa.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;
    proxy_buffering off;

    location / {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app tofa;
        set $upstream_port 33333;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

SWAG's shared proxy.conf already passes WebSockets through, and the proxy_buffering off; line above keeps video streaming. tofa needs its own subdomain (it does not work under a path like /tofa), and the apps sign in on their own, so leave SWAG's Authelia, Authentik, LDAP and basic auth options off for this host. Then add https://tofa.yourdomain.tld to your custom access URLs, as described at the top of this page.

Check it worked#

Open Admin, Settings, Remote Access. Each address you add is listed with a badge. We probe it from the cloud on the server's next check-in, so a new address turns Verified within about a minute.

A Tailscale address never gets a verdict, and that is fine. We probe from outside your network, and a .ts.net name or any other private address is only reachable from inside it, so there is nothing for us to reach. Those entries show Private network instead, or Not verified yet on older versions. Neither means anything is wrong: devices on your tailnet still connect through the address at full quality. The rest of this section is about public addresses, such as a tunnel or your own domain.

If a public address stays Not verified yet, work through the list at the top of this page. The usual causes are a login sitting in front of the server, a certificate the probe doesn't trust, or a tunnel pointed at a sub-path rather than the root.

Once it is verified, or once your device is on the tailnet, play something away from home and open the quality menu. The relay notice should be gone, and the full quality ladder available. That is the proof it is being used, rather than just being reachable.

Prefer to keep it in your compose file?

The TOFA_CUSTOM_ACCESS_URLS variable takes the same addresses, separated by commas. Anything set in Settings wins over the variable.