/til/

2025 1217 Run Squid on exit nodes

I have a Hetzner VPS that I use as a Tailscale exit node for, among other things, circumventing geolocation blocks. This works great for that purpose, but causes problems with sites like Reddit, imgur, CloudFlare, and Google, which limit or block IPs from datacenters like Hetzner’s. I can only turn the exit node on or off for all traffic, not just some apps or tabs, so I have to toggle it on and off all the time. Annoying.

The other day I realized that Squid fits very nicely into this problem. The exit node already trusts that other nodes on the encrypted tailnet are authorized and tunnels traffic for them without caching by design. If I added a regular unauthenticated non-caching HTTP proxy that listened exclusively on the Tailscale address this wouldn’t open up any security holes and would be extremely simple to configure.

And the key: applications can often be configured individually to use a proxy server or not, so unlike the exit node, I can run a proxied application and/or browser profile on the same device as an unproxied one.

How to

I deployed Squid to my exit node with this configuration:

Squid configuration file
# Squid configuration for a Tailscale open proxy

# Replace 100.x.y.z with the Tailscale IP address
http_port 100.x.y.z:3128

# Only allow traffic from the Tailscale CGNAT range
acl pprox_allow_net src 100.64.0.0/10

# Safe ports (standard ports we allow CONNECT to)
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443

acl CONNECT method CONNECT

# Deny requests to unsafe ports
http_access deny !Safe_ports
# Deny CONNECT to non-SSL ports
http_access deny CONNECT !SSL_ports

# Allow requests from pprox_allow_net network
http_access allow pprox_allow_net

# Deny all other access
http_access deny all

# Disable caching
cache deny all
cache_mem 0 MB
maximum_object_size 0 KB

# Enable logging if you need it
#access_log daemon:/var/log/squid/access.log
#cache_log /var/log/squid/cache.log

# Disable via header to be more transparent
via off

Here’s a simple example with curl, using your exit node’s MagicDNS name:

# Get the public IP address from my ISP
curl https://icanhazip.com

# Get the public IP address of my exit node
curl -x http://MACHINE.TAILNET.ts.net:3128 https://icanhazip.com

I wrote an ansible role to deploy this to my exit node (tip of master, specific commit).

Application support

Most applications can configure their own proxies:

  • Firefox connection settings are set per-profile
  • Chrome with the FoxyProxy extension can configure a proxy, and Chrome extensions are inherently per-profile
  • iCab Mobile for iOS can set a proxy of its own, separate from other apps
  • curl can use a proxy via the HTTP_PROXY= environment variable or -x command line flag
  • youtube-dl can use a proxy via the --proxy command line flag

Caveats

  • Some applications, including Safari on macOS and iOS, cannot set proxies independent of the system
  • This only works for normal HTTPS TCP-over-443 traffic like HTTPS 1.1, HTTP/2, and WebSockets; it cannot handle other traffic like QUIC HTTP/3, WebRTC (often used for telephony), or WebTorrent.

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).