Requirements and Hosting

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production), with WordPress 7.2 in beta and planned for 2026-12-08, WP-CLI 2.12.0, and checked on 2026-10-28. WordPress 7.x needs PHP 7.4 or later and recommends PHP 8.3 or later. It needs a MySQL-compatible database and, in practice, HTTPS. The page lists each requirement and the PHP extensions and settings behind it. It also records where the three official requirement pages disagree and gives a recommendation for each disagreement. The second half covers choosing a host for the Lumen Press site: hosting types, how to evaluate them, what an uptime percentage means in minutes, and the DNS records a WordPress site needs.

It is written for site owners choosing a host and for administrators preparing a server. Installing WordPress itself is covered in Installation, and running it on a laptop in Local Development Environments.

The requirements at a glance

Component Recommended (new installations) Still works, but not recommended

PHP

8.3 or later (wordpress.org); 8.4 or later (Hosting Handbook)

7.4 to 8.2. 7.4, 8.0 and 8.1 are end-of-life upstream and are supported by WordPress for backward compatibility only

Database

MySQL 8.4 LTS, or MariaDB 11.8, 11.4 or 10.11 LTS

MySQL 5.5.5 or later. The requirements page calls those older versions end-of-life

Web server

Apache HTTPD 2.4 or nginx 1.30/1.31

Any server that runs PHP. Angie, LiteSpeed and OpenLiteSpeed are known to work well

HTTPS

A TLS certificate on every host name

Plain HTTP works, but application passwords and many browser features need HTTPS

Memory (memory_limit)

256M; 512M or more for e-commerce and page builders

128M for a bare install

WordPress 7.0 raised the minimum PHP version from 7.2.24 to 7.4 and dropped PHP 7.2 and 7.3. WordPress 7.1 kept the same minimum. A site on PHP 7.2 or 7.3 cannot run 7.x and must upgrade PHP first. See Versions and Release Cycle for the PHP compatibility table across releases.

PHP

All of WordPress core, its themes and its plugins run on PHP, so the PHP version decides both security and speed. The Hosting Handbook lists WordPress 7.1 as fully compatible with PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5. The Core team has retired the "compatible with exceptions" label (April 2025) and the "beta support" label (May 2026), so a PHP version is now either supported or not.

The upstream PHP support status at the check date:

PHP End of life Status on 2026-10-28

8.5

2029-12-31

Active support

8.4

2028-12-31

Active support. The Hosting Handbook recommends it for production

8.3

2027-12-31

Security fixes only (since 2025-12-31). The wordpress.org recommended minimum

8.2

2026-12-31

Security fixes only; reaches end of life in two months

8.1 and older

Past

Unsupported upstream. Some Linux distributions still backport fixes

A known regression affects two of the 21 October 2026 security releases. PHP 8.4.27 and 8.5.12 can crash when the garbage collector frees a SimpleXML object whose properties were read as a whole. Reading the properties as a whole means calling get_object_vars(), casting with (array) or calling json_encode() on the object. PHP-FPM then logs that a child exited on a signal, such as SIGABRT. The fix ships in 8.4.28 and 8.5.13. The Hosting Handbook advises against downgrading, because 8.4.26, 8.5.11 and earlier releases have the vulnerabilities that the October releases fix. If a site cannot wait for 8.4.28 or 8.5.13, the handbook suggests PHP 8.3.36, which has the security fixes without the regression. At the check date, the official wordpress:cli-php8.4 Docker image reported PHP 8.4.27, so container users are affected too.

Upgrade PHP on staging first. WordPress core is compatible with each listed version, but older plugins and themes are often not. PHP 8 turns many old warnings into TypeError exceptions, and PHP 8.2 deprecated dynamic properties. Hosts should let each environment choose its PHP version separately, so that a newer version can be tested on staging before production. PHP: What’s New and Migration covers the language changes.

PHP extensions

WordPress uses PHP extensions when they are available. When a preferred extension is missing, WordPress either uses a slower fallback or turns the feature off.

Group Extensions (Hosting Handbook)

Required

json, hash, mysqli (the mysqlnd driver alone is not enough). pcre is built into PHP

Highly recommended

curl, dom, exif, fileinfo, igbinary, imagick, intl, mbstring, openssl, xml, zip

For caching (one of apcu, memcached, redis)

apcu, memcached, redis, plus opcache

Optional

timezonedb

Fallbacks

bc, filter, GD (image), iconv, shmop, simplexml, sodium, xmlreader, zlib

File changes when files are not writable

ssh2, ftp, sockets

The extensions are only part of what the server needs. The handbook also recommends ImageMagick 7.1 or later, which the imagick extension requires, and Ghostscript 10 or later, which PDF thumbnails need. Image uploads in WebP and AVIF need libraries that support those formats, and curl should be 8.4 or later. It recommends OpenSSL 3.5 LTS, because upstream support for OpenSSL 3.0 ended on 2026-09-07. On Ubuntu 24.04 and later and on Debian 13, the tzdata-legacy package is needed when a site’s time zone uses a deprecated name such as Europe/Kiev. Without it, the site fails with a fatal error. PHP: Configuration and Extensions explains how extensions are loaded.

PHP settings

PHP reads its settings from php.ini. Most hosts also read a per-directory .user.ini file (with PHP-FPM or CGI), or php_value lines in .htaccess (with mod_php). The settings that matter most for WordPress are:

  • memory_limit, the most memory one request may use. WordPress raises it at runtime with ini_set(), to WP_MEMORY_LIMIT on load and through wp_raise_memory_limit() for admin, cron and image work. It never lowers a higher php.ini value. WP_MEMORY_LIMIT defaults to 40M (64M on Multisite) and WP_MAX_MEMORY_LIMIT to 256M. Both constants only work where the host lets PHP change the limit at runtime; otherwise the php.ini value is the limit. WordPress core documents no minimum, but the Hosting Handbook suggests 128M at least, 256M as the default and 512M or more for heavy sites.

  • upload_max_filesize and post_max_size limit a single file and a whole request. post_max_size must be at least upload_max_filesize, and it applies to every request, not only uploads. The Media Library shows the smaller of the two values as "Maximum upload file size" (wp_max_upload_size()). In front of PHP, the web server has its own limit, such as client_max_body_size in nginx, which defaults to 1m.

  • max_execution_time limits the PHP execution time, and max_input_time limits the time spent receiving the request. Both must fit with the web server and proxy timeouts. A 300-second PHP limit is pointless behind a 60-second proxy timeout.

  • OPcache, which keeps compiled PHP bytecode in shared memory. It is the single cheapest performance win and is recommended for every production site. The settings to size are opcache.memory_consumption, opcache.interned_strings_buffer and opcache.max_accelerated_files. opcache.validate_timestamps and opcache.revalidate_freq decide when changed files are picked up. With timestamp validation off, every deployment and every core, plugin or theme update must reset OPcache, or the old code keeps running. Since WordPress 7.0, Site Health reports the opcode cache status.

The Lumen Press servers load this file after the distribution defaults:

; /usr/local/etc/php/conf.d/zz-lumen.ini (Lumen Press PHP settings)
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 72M
max_execution_time = 60
max_input_time = 60

; OPcache: on for web requests, sized for core plus a handful of plugins
opcache.enable = 1
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60

expose_php = Off

revalidate_freq = 60 means that a changed file can take up to a minute to take effect. For comparison, the official wordpress Docker image sets opcache.memory_consumption=128, opcache.interned_strings_buffer=8, opcache.max_accelerated_files=4000 and opcache.revalidate_freq=2. Those values are tuned for development; a production site with many plugins can exceed the 4000-file limit. PHP: OPcache, JIT and Performance covers OPcache in depth, and Performance and Caching covers the WordPress caching layers.

Database: MySQL and MariaDB

WordPress stores everything except files in a MySQL-compatible database, which it reaches through the mysqli extension and the wpdb class. The Hosting Handbook recommends LTS releases only. MySQL and MariaDB also publish innovation or rolling releases, which have short support windows and are not recommended for production. Managed services such as Amazon RDS and Aurora, Azure Database for MySQL, Google Cloud SQL, DigitalOcean and Percona Server are not tested regularly, but are known to work well. Use utf8mb4 with a current collation. A new MariaDB 11.8 server defaults to utf8mb4_uca1400_ai_ci, as the check below shows. Database Schema describes the tables.

Where the official pages disagree

Three official pages state the requirements, and on the check date they disagreed:

Page PHP Database

wordpress.org/about/requirements (last modified 2026-05-07)

8.3 or greater; "also works" with 7.4+

MySQL 8.0+ or MariaDB 10.11+; "also works" with MySQL 5.5.5+

Advanced Administration Handbook, "Before You Install"

7.4 or greater

MySQL 5.7 or MariaDB 10.3 or greater

Advanced Administration Handbook, "PHP" (performance)

7.4 is "officially supported", 8.0/8.1 "compatible with exceptions", 8.2 "beta support"

(not covered)

Hosting Handbook, "Server Environment"

8.4 or later for production

MySQL 8.4 or 8.0 LTS; MariaDB 11.8, 11.4, 10.11 or 10.6 LTS

Hosting Handbook, "WordPress 7.1 Server Compatibility"

8.4.x or 8.5.x for new installs; 7.4+ absolute minimum

MySQL 8.4.x; MariaDB 10.11.x, 11.4.x or 11.8.x. Lists MySQL 9.7 and MariaDB 12.3 as LTS. Absolute minimum MySQL or MariaDB 5.5.5

The disagreements go beyond the headline numbers:

  • The "Before You Install" database line is outdated. MySQL 5.7 reached end of life in October 2023 and MariaDB 10.3 in May 2023, and both are below the 8.0 and 10.11 that the requirements page names.

  • The Advanced Administration "PHP" page still uses support labels that the Core team retired in 2025 and 2026. Its statement that 7.4 is the officially supported version predates the PHP 8.x compatibility work.

  • The "Server Environment" page still lists MySQL 8.0 as recommended, although its own table gives April 2026 as the end-of-life date. It also still lists MariaDB 10.6, which reached end of life on 2026-07-06. The 7.1 compatibility post says MySQL 8.0 "should no longer be used" and no longer lists MariaDB 10.6.

  • The two Hosting Handbook pages give different end-of-life dates for MariaDB 11.8: June 2030 on "Server Environment" and 2028-06-04 in the 7.1 post.

  • The extension list on "Server Environment" says it assumes PHP 7.2, the minimum at the time it was written.

  • The legacy minimum on the requirements page names MySQL 5.5.5 but not MariaDB; the 7.1 post gives 5.5.5 for both.

The recommendation for Lumen Press, and for any new site, is to follow the newest of the pages. Use PHP 8.4, or 8.5 once the plugins in use support it, and MySQL 8.4 LTS or MariaDB 11.8 LTS. Treat MySQL 8.0, MariaDB 10.6 and PHP 8.2 as migrations to plan now. The lower numbers only describe what WordPress still runs on; they are not a target.

SQLite

Core supports only MySQL and MariaDB in production. SQLite is available through the SQLite Database Integration feature plugin (version 3.1.2 at the check date). The plugin requires WordPress 6.4 or later, PHP 7.2 or later, the PDO SQLite extension and SQLite 3.37.0 or newer. It is a community feature plugin meant to collect feedback for a possible core merge, and the Hosting Handbook calls it opt-in and experimental. WordPress Playground runs on it, which is the main place most people meet it. See Playground and Blueprints.

The plugin’s limits matter before trying it on a real site:

  • SQLite allows one writer at a time, so a site with many concurrent writes is better served by MySQL or MariaDB.

  • Activating it starts a new, empty database. Existing content is not migrated, and content created in SQLite does not move back when the plugin is turned off.

  • The database file lives in a randomized path under wp-content/database/, exposed at runtime as the DB_PATH constant. The plugin page recommends defining DB_PATH in wp-config.php as an absolute path outside the web root. The older DB_DIR and DB_FILE constants are deprecated.

  • WAL mode is the default, so backups must also copy the -wal and -shm side files, or be taken with SQLite’s own backup tooling.

Web server

Any web server that runs PHP can run WordPress. The Hosting Handbook recommends Apache HTTPD 2.4 and nginx 1.30 (stable) or 1.31 (mainline). It lists Angie 1.12, LiteSpeed Web Server 6.3 and OpenLiteSpeed 1.8 or 1.9 as known to work well. Apache reads the rewrite rules that WordPress writes into .htaccess (mod_rewrite). nginx ignores .htaccess, so its server block needs a try_files rule instead. Configuration details, including Apache 2.4 access syntax, are in Web Servers: Apache and nginx.

Upload limits are the setting that most often conflicts between PHP and the web server. The Lumen Press nginx server block raises client_max_body_size to match post_max_size, redirects HTTP to HTTPS, and tells PHP that the request was secure:

server {
    listen 80;
    server_name lumenpress.example www.lumenpress.example;
    return 301 https://lumenpress.example$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name lumenpress.example;

    ssl_certificate     /etc/nginx/tls/lumenpress.example.crt;
    ssl_certificate_key /etc/nginx/tls/lumenpress.example.key;

    root /var/www/lumen;
    index index.php;

    # Must be at least PHP's post_max_size, or nginx answers 413 first.
    client_max_body_size 72m;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS on;
        fastcgi_read_timeout 60s;
        fastcgi_pass 127.0.0.1:9000;
    }
}

When the upload is larger than client_max_body_size, nginx answers 413 Request Entity Too Large before PHP sees the request. When it is larger than post_max_size, PHP discards the body and WordPress reports a generic upload failure. When it exceeds only upload_max_filesize, the Media Library shows the file-size error.

HTTPS

The requirements page lists HTTPS support as a requirement. WordPress is fully compatible with HTTPS once a TLS certificate is installed, and several features depend on it. Application passwords are offered only over HTTPS by default. Browsers restrict service workers, geolocation and other APIs to secure origins. Search engines and browsers also flag plain-HTTP pages. Certificates are free from ACME certificate authorities, and most hosts automate them.

Three things must agree for a site to work over HTTPS:

  • the site URLs (Settings > General, or WP_HOME and WP_SITEURL in wp-config.php) start with https://

  • the web server redirects HTTP to HTTPS, so there is one canonical address

  • PHP knows that the request was secure. is_ssl() checks $_SERVER['HTTPS'] and the port, so when TLS ends at a load balancer or CDN, PHP sees plain HTTP and the dashboard enters a redirect loop

FORCE_SSL_ADMIN forces the login screen and the dashboard over HTTPS (the older FORCE_SSL_LOGIN was deprecated in WordPress 4.0). Behind a proxy, wp-config.php must translate the proxy’s X-Forwarded-Proto header:

<?php
// Excerpt of wp-config.php, above the "That's all, stop editing!" line.
// Lumen Press production: TLS ends at the load balancer, PHP sees plain HTTP.
define( 'WP_HOME', 'https://lumenpress.example' );
define( 'WP_SITEURL', 'https://lumenpress.example' );
define( 'FORCE_SSL_ADMIN', true );

// Trust X-Forwarded-Proto only because the origin accepts traffic from the load balancer alone.
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
	&& false !== strpos( $_SERVER['HTTP_X_FORWARDED_PROTO'], 'https' ) ) {
	$_SERVER['HTTPS'] = 'on';
}

// Ask PHP for more memory where the host allows runtime changes.
define( 'WP_MEMORY_LIMIT', '128M' );
define( 'WP_MAX_MEMORY_LIMIT', '256M' );

strpos() rather than str_contains() keeps the file valid on PHP 7.4, because wp-config.php runs before the WordPress polyfills load. The header can hold a list such as http,https, which is why the code searches for https instead of comparing the whole value. Trusting the header is safe only when clients cannot reach the origin directly. Otherwise any client can claim to be on HTTPS. Moving an existing site from HTTP to HTTPS also means rewriting the stored http:// URLs, which Importing, Exporting and Migrating does with wp search-replace. HSTS and the other security headers are covered in Security Hardening.

Checking a server

Site Health (Tools > Site Health, WordPress 5.2 and later) reports the PHP version, the database server, missing extensions, the memory and upload limits and the HTTPS status, and flags anything below the recommendations. The same data is available from the command line, which is useful before a site exists or in a deployment pipeline. Lumen Press keeps this check in its operations repository and runs it with wp eval-file:

<?php
/**
 * Lumen Press environment check.
 *
 * Run with: wp eval-file lumen-env-check.php
 * Compares the server against the WordPress requirements and the
 * Hosting Handbook recommendations. Exits with status 1 on a hard failure.
 */

$failures = 0;

$report = static function ( string $status, string $label, string $detail ) use ( &$failures ): void {
	if ( 'FAIL' === $status ) {
		++$failures;
	}
	WP_CLI::line( sprintf( '[%-4s] %-22s %s', $status, $label, $detail ) );
};

// PHP: 7.4 is the hard minimum, 8.3 is the recommendation on wordpress.org.
$php = PHP_VERSION;
if ( version_compare( $php, '7.4', '<' ) ) {
	$report( 'FAIL', 'PHP version', "$php (WordPress 7.x needs 7.4 or later)" );
} elseif ( version_compare( $php, '8.3', '<' ) ) {
	$report( 'WARN', 'PHP version', "$php (8.3 or later is recommended)" );
} else {
	$report( 'OK', 'PHP version', $php );
}

// Database: MySQL 8.0+ or MariaDB 10.11+ is recommended.
global $wpdb;
$server_info = $wpdb->db_server_info();
$is_mariadb  = false !== stripos( $server_info, 'mariadb' );
$db_version  = $wpdb->db_version();
$wanted      = $is_mariadb ? '10.11' : '8.0';
$engine      = $is_mariadb ? 'MariaDB' : 'MySQL';
$report(
	version_compare( $db_version, $wanted, '>=' ) ? 'OK' : 'WARN',
	'Database',
	"$engine $db_version (recommended $wanted or later)"
);

// PHP extensions.
foreach ( array( 'json', 'hash', 'mysqli' ) as $ext ) {
	$report( extension_loaded( $ext ) ? 'OK' : 'FAIL', "ext/$ext", 'required' );
}
$recommended = array( 'curl', 'dom', 'exif', 'fileinfo', 'igbinary', 'imagick', 'intl', 'mbstring', 'openssl', 'xml', 'zip' );
$missing     = array_values( array_filter( $recommended, static fn ( $ext ) => ! extension_loaded( $ext ) ) );
$report(
	$missing ? 'WARN' : 'OK',
	'Recommended exts',
	$missing ? 'missing: ' . implode( ', ', $missing ) : 'all loaded'
);

// Memory and uploads.
$memory = wp_convert_hr_to_bytes( ini_get( 'memory_limit' ) );
$report(
	( -1 === $memory || $memory >= 256 * MB_IN_BYTES ) ? 'OK' : 'WARN',
	'memory_limit',
	ini_get( 'memory_limit' ) . ' (256M recommended)'
);
$report( 'INFO', 'WP_MEMORY_LIMIT', WP_MEMORY_LIMIT . ' / WP_MAX_MEMORY_LIMIT ' . WP_MAX_MEMORY_LIMIT );
$report( 'INFO', 'Max upload size', size_format( wp_max_upload_size() ) );

// OPcache (the CLI usually has it off; check the web SAPI too).
$opcache = function_exists( 'opcache_get_status' ) && is_array( @opcache_get_status( false ) );
$report( $opcache ? 'OK' : 'INFO', 'OPcache (this SAPI)', $opcache ? 'enabled' : 'not active for ' . PHP_SAPI );

// HTTPS.
$home = home_url();
$report( str_starts_with( $home, 'https://' ) ? 'OK' : 'WARN', 'Home URL scheme', $home );

if ( $failures > 0 ) {
	WP_CLI::error( "$failures hard requirement(s) not met." );
}
WP_CLI::success( 'Environment check finished.' );

Run against the local stack described below (the wordpress:cli-php8.4 image, the zz-lumen.ini file and MariaDB 11.8), the script printed:

$ wp eval-file lumen-env-check.php
[OK  ] PHP version            8.4.27
[OK  ] Database               MariaDB 11.8.9 (recommended 10.11 or later)
[OK  ] ext/json               required
[OK  ] ext/hash               required
[OK  ] ext/mysqli             required
[WARN] Recommended exts       missing: igbinary
[OK  ] memory_limit           -1 (256M recommended)
[INFO] WP_MEMORY_LIMIT        128M / WP_MAX_MEMORY_LIMIT 256M
[INFO] Max upload size        64 MB
[INFO] OPcache (this SAPI)    not active for cli
[WARN] Home URL scheme        http://localhost:8080
Success: Environment check finished.

Three results need care when reading the output. memory_limit reads -1 inside WP-CLI once WordPress has loaded, although the same php.ini gives 256M with --skip-wordpress. The web value must therefore be read from Site Health or from a phpinfo() page. OPcache is normally off for the command-line SAPI (opcache.enable_cli), which says nothing about PHP-FPM. The PHP version, 8.4.27, is the release with the SimpleXML regression described above. str_starts_with() is safe here on PHP 7.4, because WordPress has polyfilled it since 5.9.

The database version and the WP-CLI environment can also be read directly:

wp core version --extra
wp db query "SELECT VERSION() AS server, @@character_set_server AS charset, @@collation_server AS collation;"
wp cli info

On the test stack, wp db query returned 11.8.9-MariaDB-ubu2404, utf8mb4 and utf8mb4_uca1400_ai_ci.

The Compose file below runs WordPress 7.1.3 on PHP 8.4 with Apache, on MariaDB 11.8, with the Lumen Press PHP settings mounted. The WP-CLI service is used for the checks above. The database password comes from the environment and is never written in the file.

# compose.yaml  (start with: LUMEN_DB_PASSWORD=<choose-one> docker compose up -d)
name: lumen-press-requirements

services:
  db:
    image: mariadb:11.8
    environment:
      MARIADB_DATABASE: lumen
      MARIADB_USER: lumen
      MARIADB_PASSWORD: ${LUMEN_DB_PASSWORD:?set LUMEN_DB_PASSWORD}
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      retries: 20

  wordpress:
    image: wordpress:7.1.3-php8.4-apache
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: lumen
      WORDPRESS_DB_USER: lumen
      WORDPRESS_DB_PASSWORD: ${LUMEN_DB_PASSWORD}
      WORDPRESS_CONFIG_EXTRA: |
        define( 'WP_ENVIRONMENT_TYPE', 'local' );
        define( 'WP_MEMORY_LIMIT', '128M' );
        define( 'WP_MAX_MEMORY_LIMIT', '256M' );
    volumes:
      - wp-data:/var/www/html
      - ./php/zz-lumen.ini:/usr/local/etc/php/conf.d/zz-lumen.ini:ro

  cli:
    image: wordpress:cli-2.12.0-php8.4
    profiles: ["tools"]
    user: "33:33"
    depends_on:
      - wordpress
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: lumen
      WORDPRESS_DB_USER: lumen
      WORDPRESS_DB_PASSWORD: ${LUMEN_DB_PASSWORD}
    volumes:
      - wp-data:/var/www/html
      - ./php/zz-lumen.ini:/usr/local/etc/php/conf.d/zz-lumen.ini:ro
      - ./tools:/tools:ro

volumes:
  db-data:
  wp-data:

After docker compose up -d, run docker compose run --rm cli wp eval-file /tools/lumen-env-check.php once the installer has been completed. Tear the stack down with docker compose down -v. The cli service runs as user 33:33, the www-data user of the Debian-based Apache image, so files that WP-CLI writes stay writable by Apache. The Alpine-based CLI image’s own www-data user has the ID 82. Local Development Environments compares this setup with wp-env and Playground, and Docker: Compose Fundamentals explains the Compose file format.

Hosting types

Self-hosted WordPress runs anywhere the requirements above are met. The hosting choice decides who maintains the operating system, PHP, the database, backups and caching. The trade-off is between control on one side and less work and cost on the other.

Type What is shared or managed Good for Watch for

Shared hosting

One server and one PHP pool shared by many customers; the host runs everything

Small sites with low traffic and a small budget

Old PHP versions, low memory_limit, no SSH or WP-CLI, "noisy neighbours", PHP not running as the account’s own user

VPS or dedicated server

A virtual or physical machine; the customer runs the OS, PHP, database and web server

Teams that can administer Linux and want full control

Security updates, backups, monitoring and TLS renewal are the customer’s job

Managed WordPress hosting

A platform tuned for WordPress: PHP, database, page cache, backups, staging and often core updates are managed

Content teams that want little server work

Plugin restrictions, custom caching layers, price per site or per visit, export and lock-in terms

Cloud platform (IaaS or PaaS)

Building blocks such as virtual machines, managed MySQL, object storage and CDN, combined by the customer

Sites that must scale or need high availability

Design and cost complexity, shared uploads across servers, egress fees

Containers (Docker, Kubernetes)

An image per release, with a database and uploads on persistent storage

Repeatable deployments and many environments

wp-content/uploads must be shared, core updates happen by rebuilding the image, not from the dashboard

WordPress.org keeps a page of hosts that sponsor the project. The Advanced Administration Handbook also has a page of install guides from cloud providers. Neither page is a requirement, and this sub-section does not endorse any host. Running WordPress in containers is covered in Containers, Docker and Kubernetes, and multi-server designs are covered in Scaling and High Availability. The cloud-platform row of the table below, built as a managed database plus stateless containers on several Kubernetes nodes, is described in Cloud Reference Architecture.

Lumen Press runs its staging and production environments on two virtual machines. Each has nginx and PHP-FPM 8.4, and production uses a managed MariaDB 11.8 database. A Redis object cache and a CDN for media sit in front, and the backups are stored off-site. Staging uses the same versions as production, so a PHP or database upgrade is always rehearsed there first.

Evaluating a host

These questions separate hosts better than marketing pages do:

  • Versions: which PHP versions can be selected per site, how fast new PHP minor releases are applied (for example 8.4.28 after the regression above), which MySQL or MariaDB version runs, and whether it is an LTS release.

  • Limits: memory_limit, upload_max_filesize and post_max_size, PHP worker count, CPU and inode quotas, and whether the limits can be raised.

  • Access: SSH, WP-CLI, Git or SFTP deployment, and database access. Check whether PHP runs as the account’s own user. The requirements page recommends that, because it keeps one site from reading another’s files.

  • Security: free automated TLS, isolation between accounts, a web application firewall, malware scanning, and how compromised sites are handled.

  • Operations: daily off-site backups with tested restores and a retention period, one-click staging, server-level page caching and a persistent object cache (Redis or Memcached), and whether WordPress cron can be driven by a system cron.

  • Exit: whether a full export (files plus database) is possible at any time, and with no proprietary plugins that the site will depend on.

  • Support and service level: the uptime percentage in the contract and what compensation it gives, support hours, and the status page.

Uptime arithmetic

An uptime guarantee is easier to judge as minutes of allowed downtime. The figures use a 365.25-day year and an average month of one twelfth of a year:

Uptime Downtime per year Per month Per week

99%

87.7 hours (3.65 days)

7.3 hours

1.7 hours

99.5%

43.8 hours

3.7 hours

50.4 minutes

99.9%

8.77 hours

43.8 minutes

10.1 minutes

99.95%

4.38 hours

21.9 minutes

5.0 minutes

99.99%

52.6 minutes

4.4 minutes

1.0 minute

99.999%

5.3 minutes

26 seconds

6 seconds

The formula is downtime = (1 - uptime) x period. Two details matter more than the headline number. The first is what the contract excludes, such as scheduled maintenance and "network" outages. The second is how availability is measured. A host can be "up" while PHP returns errors, so Lumen Press checks a real page with an external monitor. Availability also multiplies across dependencies. A site that needs the host (99.9%) and an external database (99.9%) is at best about 99.8% available, which is roughly 17.5 hours of downtime a year.

Domains and DNS basics

A domain is registered through a registrar, and its DNS records are served by name servers. The name servers can be the registrar’s, the host’s or a DNS provider’s. A WordPress site needs only a few record types:

Record Use

A / AAAA

Map a name to the server’s IPv4 or IPv6 address. Used for the apex domain (lumenpress.example)

CNAME

Make a name an alias of another name (www pointing to the apex, or to a CDN host name). Not allowed at the apex; some DNS providers offer a flattened ALIAS or ANAME instead

MX, TXT

Mail delivery, and SPF, DKIM and DMARC records for the mail that WordPress sends. See Email Delivery

CAA

Limits which certificate authorities may issue certificates for the domain

NS

Delegate the zone, or a subdomain, to name servers

The Lumen Press zone uses the documentation address ranges reserved for examples:

$ORIGIN lumenpress.example.
$TTL 3600
@        IN  A      192.0.2.10
@        IN  AAAA   2001:db8::10
www      IN  CNAME  lumenpress.example.
staging  IN  A      192.0.2.20
media    IN  CNAME  lumen-media.cdn.example.
@        IN  MX     10 mail.lumenpress.example.
@        IN  TXT    "v=spf1 include:_spf.mail-provider.example -all"
@        IN  CAA    0 issue "letsencrypt.org"

Lower the TTL (for example to 300 seconds) a day before moving a site to a new server. The old records then expire from resolvers quickly, and the TTL can be raised again after the move. WordPress stores its own address in the siteurl and home options, so changing the domain also means updating those options and the stored URLs. The DNS change alone is not enough; see Importing, Exporting and Migrating. A Multisite network that uses subdomains also needs a wildcard record (*.lumenpress.example), described in Multisite Networks.

Where older books are outdated

The three books consulted for this sub-section were written for WordPress 6.2 to 6.6, and several of their requirement and hosting statements no longer hold:

  • WordPress All-in-One For Dummies gives PHP 7.4+, MySQL 5.7+ and MariaDB 10.4+ as the baseline. PHP 7.4 is still the absolute minimum, but the recommendation is now 8.3 (wordpress.org) or 8.4 (Hosting Handbook). MySQL 5.7 and MariaDB 10.4 are end-of-life; the recommendation is MySQL 8.4 LTS or MariaDB 10.11 or later.

  • Ultimate WordPress Handbook says that most themes require PHP 8.2 or later. Themes declare their own minimum with the Requires PHP header, and WordPress itself requires 7.4. PHP 8.2 reaches end of life at the end of 2026, so it is no longer a sensible target either way.

  • Pro WordPress recommends tuning the MySQL query cache. MySQL 8.0 removed the query cache, and MariaDB disables it by default. The book also describes caching and minification as built into WordPress. Core has neither a page cache nor CSS and JavaScript concatenation or minification. It relies on OPcache, a persistent object cache drop-in and server or plugin page caching.

  • Advice to enable HTTP/2 Server Push for WordPress assets is obsolete, because browsers have removed support for it. HTTP/2 and HTTP/3 themselves remain worth enabling.