HostlessWP
Under the hood

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

Visitor ──► Cloudflare edge ──► static files (theme and plugin assets, wp-includes) │ ├──────► page cache (Workers KV), for visitors who are not logged in │ └──────► Worker next to your database php-wasm (PHP 8.3) + OPcache + precompiled scripts WordPress core, your theme and plugins in memory │ ├──► Hyperdrive ──► your MySQL (SSL or Tunnel) └──► your S3 bucket (wp-content/uploads)
  1. 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.

  2. 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.

  3. 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.

  4. After the response, uploads are saved

    Files WordPress wrote to wp-content/uploads during 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_VAR optimizer hints.
  • SELECT @@variable is answered from SHOW VARIABLES.
  • SQL_CALC_FOUND_ROWS paging runs as a parallel COUNT, 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

LimitValueWhat it means for WordPress
Memory per instance128 MBPHP, the code bundles and the JavaScript runtime share it. WooCommerce with the common plugins fits; very heavy plugin stacks can go over.
Static asset size25 MiB per fileLarger theme or plugin files are left out of the static assets. Media files go to the bucket instead (up to 2 GB).
Upload size32 MB by defaultConfigurable with s3.maxUploadMb; the Worker request size limit still applies.
Free plan daily limits100,000 requestsAlso 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.