Clusterward
← Back to the blog
OperationsUpdated Florian Apel

WordPress on Kubernetes: seven traps between the chart and the first page

WordPress runs well on Kubernetes if a few things are right from the start. Seven traps almost everyone hits once during their first setup, and how to avoid them.

Cover image: WordPress on Kubernetes: seven traps between the chart and the first page

WordPress wasn’t written for Kubernetes. It expects a server with a disk, a database next door and an administrator who takes care of the rest. Even so, it runs very well on Kubernetes if a few things are right from the start. Almost everyone runs into the following seven traps at some point, usually within the first few hours after their first helm install.

1. The open installer

A fresh WordPress installation shows the installer at its address: site title, admin user, password. Whoever fills it out first owns the site. That sounds theoretical, but it isn’t. Automated scanners specifically look for fresh installations, and between the first deployment and the moment you open the installer yourself, minutes often pass, sometimes hours, for example while the certificate is being issued. Whoever gets there first in that window has an admin login and the database.

The fix is simple: until the installer has run, the site sits behind a password, for example via basic auth on the ingress. After that, the protection is removed. The only thing that matters is that it’s in place from the start, not added later.

2. WP-Cron that only runs when visitors show up

WordPress schedules tasks with WP-Cron: scheduled posts, update checks, plugin tasks. But they only run when someone visits the site. A site with few visitors therefore publishes a scheduled post hours late, while a site with many visitors kicks off unnecessary checks on every request.

Kubernetes has the better solution built in: a CronJob that calls wp-cron.php every few minutes, and DISABLE_WP_CRON in the configuration so visitor requests no longer count.

3. One disk, one instance

WordPress writes uploads, themes and plugins to its wp-content directory. On Kubernetes, that directory lives on a PersistentVolume, on Scaleway on Block Storage, and Block Storage is attached to exactly one node. Two pods on two nodes can’t use the same disk at the same time.

The consequences: WordPress runs as a single instance. A rollout has to stop the old instance before the new one starts, otherwise the new one waits forever for the disk. And horizontal scaling needs a different approach: move uploads to Object Storage, for example with a plugin, and put the rest into the image. For most sites, a single instance with enough resources and a cache in front of it is more than enough.

4. The redirect loop behind the ingress

Behind an ingress controller, WordPress speaks HTTP internally while the visitor speaks HTTPS. If WordPress doesn’t know the original request was encrypted, it redirects to HTTPS, the ingress passes HTTP through again, and the browser reports too many redirects. The official WordPress image evaluates the X-Forwarded-Proto header that ingress-nginx sets. Problems usually only arise from a second layer: Cloudflare in the “Flexible” SSL mode talks to the origin unencrypted. With “Full (strict)” and a valid certificate on the ingress, the loop is gone.

5. The database password in plain text

Helm charts usually expect the database password in the values. There, it ends up in the Git repository, in the Helm release in the namespace and in every ticket someone pastes the values into. Better: the password is generated, stored encrypted and only referenced in the values. And WordPress gets its own database with its own user, not the instance’s admin.

6. Uploads that hit a limit

An upload passes through several limits: the ingress controller’s maximum request size, PHP’s limits and, if there is one, those of a proxy in front. Without further configuration, ingress-nginx only lets one megabyte through, and the theme package or the video fails with a terse 413 error. If you expect uploads, set the limit on the ingress deliberately and check PHP’s limits as well.

7. Backing up only one half

A WordPress site consists of two halves: the database with posts and settings, and the disk with uploads, themes and plugins. A backup of only the database brings back posts without images; a backup of only the disk brings back images without posts. Both need to be backed up, close together in time, and both need to be restored once before you rely on them. What happens to disks from Helm charts in the process is covered in Helm charts and their disks.

What should you do when the WordPress site grows?

A single-instance site handles more than many people think. The bottlenecks are almost always in the database and in PHP, not in the number of pods. Three steps help before you think about multiple instances: a page cache as a plugin or in front of the site, an object cache for database queries, and enough CPU and memory for the one pod. Only when that no longer suffices is it worth rebuilding for multiple instances, with uploads in Object Storage and an image that contains WordPress and its plugins instead of maintaining them on the disk.

What needs to be in place before your first site goes live?

  • Installer behind a password until setup is done
  • A real cron job, WP-Cron switched off
  • One instance, rollout stops before it starts
  • Cloudflare, if in front, in “Full (strict)” mode
  • Own database, password not in plain text
  • Upload limits checked on the ingress and in PHP
  • Disk snapshots and database backups, tested once

How does the app catalog avoid these traps?

In the Clusterward app catalog, WordPress is the first template, and it takes care of most items on this list: you provide the name, domain, cluster and MySQL instance, and Clusterward creates a dedicated database with a generated password that is only referenced in the values, plus a disk with daily snapshots, a cron job every five minutes, the certificate and, if the zone is registered, the DNS record. WordPress runs with exactly one instance and, on update, is stopped first and then restarted. The disk is marked so that it survives an undeploy. Until you’ve completed the installer, the site asks for a setup password; “Open to everyone” removes it. Ingress requests may be up to 64 MB by default. From then on, the site is a normal application with logs, usage and uptime checks. Two things remain up to you: set DISABLE_WP_CRON in wp-config.php after setup so only the cron job counts, and check the SSL mode if you use Cloudflare. More in WordPress on Kubernetes; how the disk is backed up in Volumes & snapshots, and the database instance in Managed databases.

Conclusion

WordPress on Kubernetes rarely fails because of Kubernetes, but because of the assumptions WordPress makes about its server. Protect the installer, replace WP-Cron, plan the disk for a single instance and back up both halves. Then the site runs as quietly as on any other server, just with snapshots, logs and rollbacks on top.

Planning WordPress sites on Kubernetes? Tell us how many sites you run. We’ll show you how the app catalog creates them and backs them up. Ask a question →

Sources and further reading

Frequently asked questions

  • Put the site behind a password, for example via basic auth on the ingress, until the installer has run, and remove the protection afterwards. Otherwise someone else can fill out the installer first and gets an admin login and the database. Automated scanners specifically look for fresh installations.