How WordPress runs inside a Worker
WordPress core is not modified. PHP itself is compiled to WebAssembly, and a small runtime layer gives it what a server used to: a database connection, a file system, a clock, storage for media and a schedule for cron.
A request, step by step
Static files never reach PHP
CSS, JavaScript, fonts and images from WordPress, your theme and plugins are uploaded as Workers static assets and served from the Cloudflare location nearest the visitor.
Cached pages answer in milliseconds
A visitor who is not logged in and has no cart gets the page from the page cache, without starting PHP.
Everything else runs WordPress
The Worker hands the request to PHP, WordPress runs as it would on any server, and its queries go to your MySQL through the database bridge.
After the response, uploads are saved
Files WordPress wrote to
wp-content/uploadsduring the request are copied to your bucket.
PHP in WebAssembly
HostlessWP uses php-wasm from WordPress Playground: PHP 8.3 compiled to WebAssembly, the same build that runs WordPress in the browser. Inside a Worker it boots once per instance and then serves requests one after another.
- Code is packed into zip bundles. WordPress, your theme, plugins and translations are bundled at deploy time. They are mounted into PHP's in-memory file system lazily: a file is only unpacked when PHP first reads it. A request reads around 2,000 of a site's 16,000 files, and every unpacked file costs memory.
- OPcache works. php-wasm's OPcache does not start by default, because its server API is not on OPcache's list. The build patches that and keeps compiled scripts in shared memory.
- Scripts are precompiled. During deploy, hostlesswp renders a few pages locally against your database and packs the OPcache files, so a new instance does not compile 1,700 scripts before its first page.
The database bridge
PHP in WebAssembly cannot open network sockets, so it cannot talk to MySQL itself. A db.php drop-in replaces WordPress's database class and sends every query to the Worker, which runs it through Cloudflare Hyperdrive. A userland mysqli covers plugins that skip $wpdb.
Hyperdrive pools connections, which means two statements in a row can run on different server connections. WordPress and its plugins assume one connection, so the bridge carries session state with each statement:
- Session settings are attached to every query as
SET_VARoptimizer hints. SELECT @@variableis answered fromSHOW VARIABLES.SQL_CALC_FOUND_ROWSpaging runs as a parallelCOUNT, which brought the REST posts list from 2.5 s to 1.6 s in our tests.
Hyperdrive's query cache is turned off, because WordPress reads its own writes right away. A database that is not reachable from the internet is connected through a Cloudflare Tunnel run from your server.
Placement: run next to the database
A WordPress page runs dozens of queries, one after another. Each one waits for a full round trip to the database, so distance multiplies.
7.7 s
Worker near the visitor
Core home page, Worker in Singapore, database in Virginia: about 230 ms per query, 29 queries.
1.2 s
Worker next to the database
Same page, Worker placed in US East. What hostlesswp sets up, by locating your database and setting placement.region.
0.07 s
Direct connection
Same placement without Hyperdrive in between: about 2 ms per query. Our next step; it needs a TLS fix first.
PHP processing time per warm request, including database waits. Method: benchmarks.
Page cache
Starting PHP on a fresh instance takes seconds, so pages for visitors who are not logged in are kept in Workers KV and answered without starting PHP.
- Never cached: logged-in users, commenters, password-protected posts, shoppers with a cart, searches, previews, the admin, the login page, the REST API, and any response that sets a cookie or that a plugin marks as not cacheable (WooCommerce cart, checkout and account pages).
- Kept fresh: publishing, editing, approving comments, changing menus, widgets, the theme, plugins, settings or stock marks every cached page as outdated, as does each deploy. The next visitor still gets the cached copy while the page renders again in the background. No page is served older than 8 hours.
- Visible: every response carries
x-hostlesswp-cache(hit,stale,miss,bypass,skip). Turn the cache off with--no-page-cache.
Media in your bucket
A Worker has no lasting disk, so wp-content/uploads lives in an S3-compatible bucket: Cloudflare R2, AWS S3, Backblaze B2, MinIO or any other service with the S3 API.
- Every deploy copies new files from your site's uploads folder to the bucket. Nothing is ever deleted there.
- PHP writes uploads to memory as usual; after the request the Worker copies them to the bucket. Unique file names are checked against the bucket, originals are fetched when you edit an image, and deleting an attachment deletes its files.
- Media URLs stay
/wp-content/uploads/...and are served from the bucket, cached at the edge, without starting PHP. The bucket can stay private; give it a public or CDN address and new media links point there directly.
Sessions, cron and the clock
Logins stay valid
Your site's own keys and salts are deployed as a Worker secret, so cookies issued by your current host keep working on Workers.
WP-Cron on a schedule
Scheduled events run from a Cloudflare Cron Trigger every 15 minutes instead of during page views, which keeps visitors from waiting on them.
A clock that moves
Workers freeze the clock during CPU work. PHP functions that wait for it to move, like uniqid(), would spin forever, so the runtime makes the clock strictly increasing.
Outgoing HTTP
wp_remote_get() and friends go through the Worker's fetch(), so update checks, payment gateways and APIs work.
Read-only code
Installing plugins and themes from the admin is turned off, because files written there would vanish with the instance. Change code on the source site and redeploy.
Your settings, once
Answers from setup are saved in hostlesswp-config.php next to wp-config.php (mode 0600, never uploaded), so npx hostlesswp deploy asks nothing again.
Limits that shape it
| Limit | Value | What it means for WordPress |
|---|---|---|
| Memory per instance | 128 MB | PHP, the code bundles and the JavaScript runtime share it. WooCommerce with the common plugins fits; very heavy plugin stacks can go over. |
| Static asset size | 25 MiB per file | Larger theme or plugin files are left out of the static assets. Media files go to the bucket instead (up to 2 GB). |
| Upload size | 32 MB by default | Configurable with s3.maxUploadMb; the Worker request size limit still applies. |
| Free plan daily limits | 100,000 requests | Also 100,000 Hyperdrive queries and 1,000 KV writes a day. Workers Paid ($5/month) raises them. |
Read the code
The runtime is a few files: the Worker, the lazy zip file system, the media bridge and the PHP drop-ins.