Why self-host WordPress on a VPS
Shared hosting splits CPU, RAM and even the database with hundreds of other sites: your speed depends on your neighbors, and SSH access or fine server configuration are often locked down. A VPS gives you resources allocated to your machine, full control of PHP-FPM, the web server, the object cache (Redis) and the page cache. For an agency managing several client sites, it also means the ability to isolate everything in containers on a single machine, with backups and deployments under control, where shared hosting imposes its limits and its extra costs.
The gains of a WordPress on VPS
- CPU/RAM allocated to your machine: fewer slowdowns caused by neighbors
- Full control of PHP-FPM, the web server and extensions
- Redis object cache and page cache for response times cut in half
- Full SSH/WP-CLI access to automate maintenance and deployments
- Per-container isolation: several client sites on a single VPS
- Backups and security rules defined by you, without a per-site surcharge
Realistic prerequisites
A 2 vCPU / 2 GB RAM VPS comfortably handles a WordPress site with moderate traffic; move up to 4 GB with Redis, WooCommerce or several sites. You need Docker + Compose, a MariaDB or MySQL database, ideally Redis for the object cache, and a yourdomain.com domain with its SSL certificate. Plan for a bit of PHP tuning (upload_max_filesize, memory_limit) depending on your media and extensions.
Step-by-step deployment
Compose WordPress, MariaDB and Redis
In
docker-compose.yml, declarewordpress:php8.3-fpmorwordpress:apache, amariadb:11service andredis:alpine. Persistwp-contentand the database in named volumes, on an internal Docker network.Fill in the database and secrets
Pass
WORDPRESS_DB_*and strong passwords via environment variables. Add the security keys (salts) generated from the official WordPress API inWORDPRESS_CONFIG_EXTRA.Launch and install WordPress
Start with
docker compose up -dthen open your domain for the installation wizard. Better: automate it via WP-CLI (wp core install) for reproducible deployments from one site to the next.Set up the reverse proxy and SSL
Configure Nginx/Caddy in HTTPS in front of WordPress. Adjust
client_max_body_sizefor uploads, enable gzip/brotli compression and caching of static assets (images, CSS, JS) with longCache-Controlheaders.Enable the Redis object cache
Install an object cache plugin (e.g. Redis Object Cache), point it to the
redisservice, and enable it: repeated database queries are served from memory, greatly relieving MariaDB.Harden and back up
Disable file editing in the admin (
DISALLOW_FILE_EDIT), limit login attempts, and schedule a nightly backup ofwp-content+ SQL dump to remote object storage.Logging in for the first time
Open the site URL: WordPress starts its five-minute installation — language, site title, then the creation of YOUR administrator username with the password and e-mail address of your choice. Finish it without delay; an unfinished installation is there for the taking by the first visitor.
For performance that rivals the fastest managed platforms, add a full page cache upstream of PHP: a plugin generating static HTML files served directly by Nginx (via try_files), or a FastCGI cache module. Your public pages then no longer touch PHP or MariaDB at all, which lets a modest VPS absorb thousands of simultaneous visitors, while keeping the back office and the cart (WooCommerce) excluded from the cache.
PHP alert of 21 Oct 2026 (PHP2Shell): updating WordPress does not fix PHP
On 21 October 2026 the PHP project shipped security releases for two remote code execution flaws, CVE-2026-109618 and CVE-2026-109619. The fix lives in PHP itself: the patched versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Your WordPress can therefore be fully up to date (core, themes, plugins) while still running on a vulnerable PHP, because the WordPress update screen never touches the interpreter. With the stack from this article, PHP lives inside the wordpress Docker image: until you pull an image rebuilt on a patched version and recreate the container, the flaw stays open. For the details of both flaws, read the article php2shell-cve-2026-109618-109619-mettre-a-jour-php; for rebuilding images, php2shell-docker-wordpress-reconstruire-images-php.
Check, then fix, in five moves
Read the PHP version actually being served
The most reliable place is WordPress itself: Tools > Site Health > Info > Server shows the PHP version executing your pages. On a VPS without Docker, be wary of
php -v: it queries the command-line binary, which can be a different version from PHP-FPM, the one serving your visitors. Ask the service itself withphp-fpm -v(orphp-fpm8.3 -von Debian and Ubuntu).Check the version inside the container
With Docker, run
docker compose exec wordpress php -v: the image ships a single PHP, so this result holds for the container's FPM or mod_php. Compare it to the patched versions above, staying on the same branch (8.3.x must reach 8.3.36, not 8.4.27).Pull the updated image
Run
docker compose pull && docker compose up -d. The pull downloads the latest image published for your tag (for examplewordpress:php8.3-fpm), andup -drecreates the container on it. Your named volumes keepwp-contentand the database. Ifphp -vstill shows an old version, the patched image is not yet published for that tag: check back later, or move to another PHP branch if your site supports it.Restart PHP-FPM
Updating a package or files does not change the code already loaded by running processes. On a classic server, restart the service with
systemctl restart php8.3-fpm(adapt the version number); with Docker, recreating the container is enough, otherwisedocker compose restart wordpress. A plain reload is less reliable than a restart when you need every worker renewed.Look for signs of exploitation
An update closes the door; it does not tell you whether someone already walked in. Go through the Nginx or Apache access and error logs (
docker compose logs --since 72h wordpresswith Docker) looking for PHP-FPM worker crashes, repeated out-of-memory messages and unusual high-volume requests. Insidewp-content, list recently modified.phpfiles withfind wp-content -name '*.php' -mtime -7, especially underuploads, where no script should live. At the slightest doubt, restore from a clean backup and change passwords and salts.Automate the check with WP-CLI and cron
Note that
wp --inforeports the command-line PHP version, not the PHP-FPM one: keep it for scripts, not for judging web exposure. Instead, schedule a weekly cron job that runsdocker compose pull, then compares the output ofdocker compose exec wordpress php -vwith the expected version and emails you on any mismatch. That way the next PHP security alert no longer depends on your memory.Verify again
Reopen Site Health and rerun
php -vin the container: both must show a patched version. Write the date in your maintenance log, and repeat the routine for every new PHP security release.
Partial mitigation, not a fix
For CVE-2026-109619, the PHP advisory says to disable session.upload_progress.enabled in php.ini or the FPM pool, then restart all workers; ini_set() and .user.ini are not enough. This reduces exposure while you schedule the update, but only updating PHP truly fixes both flaws.
The official documentation
For advanced configuration and options specific to the tool, refer to the official WordPress documentation. This guide covers going live on a VPS; the vendor's docs remain the reference for fine-tuning, major upgrades and specific use cases.