Does a Cloudflare Tunnel keep a webhook alive when Tailscale is down?
Mira Chen
October 7, 2026
Yes, if cloudflared is running and its origin is localhost or a LAN address. A webhook from GitHub, Stripe, or a forge hits Cloudflare’s edge, and the tunnel carries it to your app. Tailscale is not on that path. Tailscale being down does not drop the webhook. The internet being down does, and so does an origin you pointed at a 100.x address, because that origin is Tailscale wearing a different name.
People stack the two tools and then treat them as one pipe. They are two pipes. The tunnel is for strangers on the internet who need to reach one hostname. Tailscale is for your devices. A webhook is a stranger. It never had a tailnet login, and it will not notice that your laptop cannot ping the NAS.
The path a webhook actually takes
The sender resolves your public hostname, which is a Cloudflare name, and posts to 443. Cloudflare terminates that and pushes the request down the outbound connection cloudflared already holds. Your home router never accepted an inbound port. The app sees a request from cloudflared on localhost or on a docker network. At no point does the packet consult Tailscale’s coordination server, a DERP relay, or MagicDNS. You can stop the tailscaled service, watch it fail on the phone, and the next webhook still lands, as long as the WAN link and cloudflared stayed up.
Test it that way, not by turning off Wi-Fi. Turning off the home WAN kills both tools and proves nothing except that both need a path out. Stop the Tailscale service, or disconnect a client, and curl the public webhook URL from a phone on cellular. A 200 from the app means the tunnel is independent. A failure means cloudflared or the origin is the problem, and you should not “fix” it by restarting Tailscale.

Retries hide a short gap and do not hide a design bug. GitHub and most payment providers retry a failed webhook for a while. A cloudflared restart of ten seconds is usually fine. An origin that only exists on the tailnet fails every retry until someone notices the delivery log is red. Check the sender’s recent deliveries after you test, not only your own access log. Your app can be healthy and the sender can have given up an hour ago.
The origin line that secretly uses Tailscale
cloudflared’s ingress origin is the whole question. http://localhost:8080 stays up when Tailscale is down. http://192.168.1.20:8080 stays up if that LAN address stays up. http://app.tailnet.ts.net, or a 100.x address, dies with Tailscale, because cloudflared has to resolve and route that name through the tailnet to reach a container that is sitting next to it. People use the MagicDNS name because it was already in the browser. It is the wrong origin for a process on the same machine. Use the docker service name or localhost. The public hostname can still be pretty. The origin should be boring and local.
A Tailscale sidecar in the same compose file is the other trap. The app’s network namespace is the sidecar, cloudflared is told to use the sidecar’s name, and “Tailscale is down” means the sidecar container is unhealthy and the app no longer has a stack. The webhook was never independent. Put the app on an ordinary docker network, publish nothing to the host except what cloudflared needs, and let Tailscale be a separate way for you to SSH. If the sidecar was how you avoided opening a port, the tunnel already avoided opening a port. You do not need both mechanisms for one webhook.

When both are down and you think it is Tailscale
Home internet down takes the tunnel with it. cloudflared cannot hold an outbound connection through a dead WAN. Tailscale cannot either. The webhook fails, the phone on cellular cannot reach the homelab, and it feels like one product failed. Look at whether the modem has a WAN address before you debug identity providers. A tunnel does not create connectivity. It reuses whatever connectivity the house still has.
A reboot of the same box takes both if both are enabled at boot and one of them is slow. cloudflared should be a service that starts without waiting for tailscaled. If a unit file orders cloudflared after Tailscale, a stuck Tailscale login delays the webhook path for no reason. Remove that ordering. The tunnel does not need the tailnet to be healthy, and it should not wait for it. After a power cut, the first thing that should answer is the public hostname, before your laptop has rejoined anything.
Cloudflare itself can be the outage, and Tailscale will look innocent because your SSH session still works. That is the mirror image and it is useful. If you can reach the app over Tailscale and the public webhook URL fails, the tunnel or the edge is the broken pipe. Do not conclude the app is down. Do not restart the app. Check cloudflared’s logs and the Cloudflare status, then the sender’s delivery log. The app, reached over the tailnet, is the control. The public name is the path under test.

Headers, time, and the signature that fails closed
A webhook that arrives and then gets rejected is not a Tailscale problem either, and it shows up the same week you change the path. Stripe, GitHub, and most serious senders sign the body and include a timestamp. If cloudflared or the proxy in front of the app buffers or rewrites the body, the signature fails and the app returns 400. The sender retries, you see traffic, and you blame the tunnel for being “flaky when Tailscale is weird.” The tunnel delivered the bytes. Your verification failed. Log the signature result separately from the connection. A 400 with a bad signature is a config bug. A connection timeout is a path bug. They are not the same page in the sender’s dashboard, and only one of them correlates with tailscaled.
Clock skew is the version of this that appears after a long outage. The home machine boots, NTP has not synced, the webhook timestamp looks five hours off, and the library rejects it as a replay. Tailscale being up or down is irrelevant. The fix is NTP before you trust signatures, and a window that matches the provider’s docs, not a second tunnel. If you only test with a curl that does not sign, you will miss this. Use the sender’s “redeliver” button after the test outage. That is the request that has a signature. Your curl does not.
Idempotency matters once retries and Tailscale tests overlap. You will fire a test event, stop tailscaled, fire another, and the provider will also retry the first. The app must tolerate duplicates. If a duplicate creates a second order or a second notification, the tunnel is working too well and the app is not. Fix the handler. Do not “solve” duplicate delivery by taking the public path down whenever you restart Tailscale. The public path is supposed to stay up. The handler is supposed to notice it has seen that event id before.
Who does not need the tunnel for this
If the webhook sender can run on your tailnet, you do not need a public tunnel. A cron you control, a second machine you own, a friend with a Tailscale invite: those can post to a private address. A company on the internet cannot. Stripe will not join your tailnet. For those senders the tunnel is the product, and Tailscale is irrelevant to them. Keep Tailscale for your admin path so you can still reach the app when you want to read the log. Keep the tunnel origin off the tailnet so their retry does not depend on your laptop’s VPN. Write the origin URL in the cloudflared config as a comment you will understand later, next to the public hostname, so the next edit does not “tidy” it back into a MagicDNS name.
Funnel is the Tailscale feature that does die when Tailscale dies. If the webhook URL is a Funnel name, the answer flips: Tailscale down means the webhook is down. Migrate that sender to the Cloudflare hostname if you want the independence this question is asking about, and remove the Funnel once the sender’s configuration is updated. Two public URLs for one webhook is how a retry hits the dead one forever. Leave the old URL in place only until the sender’s dashboard shows the new one succeeding, then delete it the same day. A forgotten Funnel hostname will keep failing quietly in a retry queue you do not check, and a month later it will look like Tailscale broke billing. It did not. You left a second door open onto a service you stopped. One public name, origin on localhost, Tailscale for you. That is the split that survives a tailnet outage without pretending it survives a dead ISP. If you cannot explain which process dies when you stop tailscaled, you do not have the split yet.