When Tailscale remote devs could not push theme changes because wp-cron refused to run over the VPN tunnel and the cron workaround I used
At Tailscale, we pride ourselves on building developer tooling that “just works” across networks — but even teams like ours sometimes stumble across edge cases that require a bit of creative thinking to resolve. Recently, our remote developers encountered a puzzling issue: theme changes in WordPress weren’t taking effect when deployed over our secure Tailscale VPN. After some investigation, it became clear that this had less to do with Tailscale itself and more to do with how WordPress’s built-in cron system behaves in VPN environments.
TL;DR
Remote developers using Tailscale for accessing WordPress environments couldn’t push theme changes because wp-cron.php calls were silently failing through the VPN tunnel. This was due to WordPress relying on incoming HTTP requests to trigger cron events, which VPN-based local domains didn’t support in this case. I solved this by setting up a manual cron job via the system’s scheduler that executes WordPress crons independently of HTTP traffic. This restored expected functionality and streamlined our theme deployment process.
Understanding the Problem
Our setup is pretty typical for many remote WordPress teams using Zero Trust networks: WordPress runs on a central server, and remote devs connect through Tailscale to push code changes, media, and new theme settings. The strange thing was, when theme files were changed or customized remotely, the actual changes wouldn’t appear on the live site — unless someone made a direct HTTP request to the server from within its own network.
There were no obvious error messages. Templates seemed to complete updating via Git, permissions were correct, and the database was intact. But something was holding WordPress back from rebuilding its theme cache. After tracing some logs and diving into WordPress internals, it became clear: wp-cron wasn’t running.
How wp-cron Works
To fully understand the issue, it’s worth highlighting how WordPress handles scheduled tasks. Unlike a traditional UNIX cron job that runs periodically regardless of requests, WordPress takes a lazy approach: it only attempts to run pending tasks when someone accesses the site.
This means that the wp-cron.php script is called during page loads — usually within the wp-load.php or similar initial scripts. If visitors stop accessing the site or if the site is being accessed in a way that doesn’t trigger HTTP requests correctly (as in our VPN case), cron events just don’t run.
The VPN Tunnel Quirk
This is where things got tricky. When a developer accessed our staging WordPress instance over Tailscale, everything would appear to function normally. Pages loaded fine, and edits appeared to go through. However, Tailscale uses point-to-point encrypted tunnels between devices — and in our setup, HTTP requests that were meant to ‘ping’ WordPress internally (like calls to https://mysite.local/wp-cron.php) didn’t resolve or trigger properly over the tunnel.
So while users could interact with the site, WordPress cron jobs were effectively non-functional. This impacted tasks like refreshing theme templates, clearing cache, and executing any scheduled post-processing we depended on.
Initial Attempts to Fix It
At first, we thought this could be solved by tweaking DNS settings or forcing HTTP resolution over the VPN interface. We tried:
- Hardcoding hostnames in the remote devs’
/etc/hosts - Creating reverse proxies to simulate internal HTTP requests
- Toggling the
DISABLE_WP_CRONflag on and off to retry scheduling behavior
But none of these had consistent, reliable results across different remote environments. The issue boiled down to this: wp-cron is not meant to be reliable without a real public or resolvable request context. A Tailscale peer doesn’t qualify as “internet-facing” under WordPress’s expectations, particularly when loopback or internal requests silently fail due to timeouts or non-response.
The Workaround That Solved It
Eventually, I decided to go the traditional route: disable WordPress’s pseudo-cron system and take control manually. Here’s the step-by-step workaround that ultimately fixed our issue:
- Disabling WordPress’s pseudo-cron. In
wp-config.php, I set:
define('DISABLE_WP_CRON', true);
- Setting up a real cron job on the server. On the staging server (where WP runs), I created a shell script:
#!/bin/bash
/usr/bin/php -f /var/www/mysite/wp-cron.php >/dev/null 2>&1
I named this run-wp-cron.sh and gave it execute permissions with chmod +x.
- Scheduling it in crontab. Using
crontab -e, I added:
*/5 * * * * /path/to/run-wp-cron.sh
Now, WordPress cron would run every 5 minutes, regardless of incoming HTTP traffic. This ensured our post-deployment scripts and theme transformations ran smoothly even when no one was actively browsing the site.
Results
Immediately after implementing this fix, our remote developers noticed their changes began propagating as expected. Theme file edits were applied and visible without anyone being inside the local network. Scheduled tasks like image compression, theme CSS build processes, and deployment hooks now all operated predictably.
Best of all, this method is completely transparent. Devs don’t need to do anything differently. And since it uses the server’s internal tools (cron), it’s inherently more reliable than relying on PHP-driven HTTP hooks.
Lessons Learned
This bug was a reminder that while VPNs like Tailscale are simple and intuitive, the services and applications run over them may still rely on legacy assumptions — like public HTTP request availability — which don’t translate well to secure, private mesh networks.
Key takeaways for teams using WordPress with VPNs:
- Always verify if wp-cron is executing as expected in your deployment pipelines, especially for headless or accessed-remotely instances.
- Manual cron with DISABLE_WP_CRON gives you granular control and removes ambiguity from time-based executions.
- VPNs can disrupt assumptions about what the “public internet” looks like to your apps — always test from within the tunnel.
Wrapping Up
In the end, this issue wasn’t about Tailscale failing — it was about WordPress’s architecture and its heavy dependence on browser or crawler visits to trigger internal function calls. In a Zero Trust world where fewer requests originate externally, fixing this required restoring control to the system level rather than relying on application hacks.
If you’re running WordPress behind Tailscale or another VPN, and your scheduled events feel “off,” try checking wp-cron logs and consider suppressing the built-in behavior in favor of a more traditional cronjob. It’s not only more predictable but often faster and cleaner.
Solving infrastructure quirks like this is satisfying, and we hope this approach smooths deployment workflows for others embracing modern remote dev setups.
Comments are closed, but trackbacks and pingbacks are open.