This project will deploy a headscale and headplane stack under the assumption that it will be fronted via a reverse proxy such as Caddy, Ngnix or Apache.
HEADSCALE_HOSTNAME=abcd12345.fastly.net docker compose up
The headscale server will expose port 5566 over plaintext HTTP for the reverse proxy to forward requests.
Tailscale agents uses the useragent Go-http-client/x.x or similar. Simple forwarding rules based on the user agent is one option.
Alternatively, the following paths at the reverse proxy should be routed to the headscale server on port 5566
/ts2021/key?v=/derp/derp/*
########### BEGIN TAILSCALE ###########
# Match /ts2021, /derp, or /key paths
RewriteCond %{REQUEST_URI} ^/(key|derp/latency-check)
RewriteRule ^(.*) http://localhost:5566$1 [P,L,NC]
RewriteCond %{REQUEST_URI} ^/(ts2021|derp)
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^(.*) ws://localhost:5566$1 [P,L,NC]
############ END TAILSCALE ############
{
# Global error logging
log {
output file /var/log/caddy/error.log
level INFO
}
on_demand_tls {
ask http://127.0.0.1:10000
}
}
# ---------------------------
# A hack to handle situations where the CDN uses
# the correct SNI but wrong Host header. So we need
# a match all host handler below and an on-demand TLS
# config so we can restrict to the origin hostname
# ---------------------------
http://127.0.0.1:10000 {
@checkOrigin query domain=abcd-origin.somewhere.net
handle @checkOrigin {
respond "OK" 200
}
handle {
respond "Forbidden" 403
}
}
# ---------------------------
# Host-less HTTPS listener with on-demand TLS
# ---------------------------
https:// {
tls {
on_demand
}
log {
output file /var/log/caddy/access.log
level INFO
}
# ts2021|derp|key → localhost:5566
@tailscale path_regexp ^/(ts2021|derp|key)
handle @tailscale {
reverse_proxy http://localhost:5566 {
transport http {
read_buffer 4096
read_timeout 0
write_timeout 0
}
}
}
# Default fallback -> Lighthouse
handle {
reverse_proxy http://somewhereelse
}
}
Tailscale will prefer UDP where possible and will try to punch a hole via STUN. If this fails, it will fallback to websockets over HTTPS. Tailscale does not use the standard websocket RFC6455 and it's framing protocol, but it's own protocol called TS2021. CDN's that support websockets typically will only passthrough RFC6455 compliant requests. Therefor you'll need to use the tailscale builds from the tailscale folder.
Headplane is setup to listen on port 3000. Operators should just use SSH port forwarding for accessing the admin console. If access via the reverse proxy is desired then mapping a route from /admin -> http://localhost:3000/admin is a possibility, but this endpoint should be secured to prevent access from unauthorized locations.
Use sudo docker exec headscale headscale apikey create to generate an API for accessing the admin console.