Requirements & Hosting

What WordPress 6.8 actually requires from a server, what you should really run, and the memory constants behind failed uploads.

Syntaxdefine( 'WP_MEMORY_LIMIT', '256M' ); // and WP_MAX_MEMORY_LIMIT for admin-side work

WordPress's declared minimum and a sane production baseline are two different numbers, and hosting sales pages blur them. Core states its floor in wp-includes/version.php: WordPress 6.8 sets $required_php_version to 7.2.24 and $required_mysql_version to 5.5.5, and needs the json and hash PHP extensions. Below that, wp-settings.php calls wp_check_php_mysql_versions() — one of the first things it does, right after requiring version.php and load.php — and aborts the request before the rest of WordPress boots.

What to actually run

ComponentCore minimum (6.8)Run this
PHP7.2.248.2 or 8.3
DatabaseMySQL 5.5.5MySQL 8.0+ or MariaDB 10.11+
Web serveranything running PHPnginx, or Apache with mod_rewrite
HTTPSnot enforced by corerequired — WordPress.org lists it as mandatory

Rewriting

Pretty permalinks need URL rewriting. On Apache that means mod_rewrite, AllowOverride permitting .htaccess, and a writable .htaccess so WordPress can maintain the block between its # BEGIN WordPress markers. nginx has no .htaccess: you add try_files $uri $uri/ /index.php?$args; to the server block yourself.

Memory

WordPress defines WP_MEMORY_LIMIT as 40M (64M on multisite) and only ever raises memory_limit towards it, never lowers it. Admin screens and image processing then call wp_raise_memory_limit(), which pushes up to WP_MAX_MEMORY_LIMIT — 256M by default. Uploads that die without an error are nearly always this, or PHP's upload_max_filesize and post_max_size, which WordPress cannot change from PHP.

Example

Example · php
<?php
/**
 * Plugin Name: Acme Reports
 * Requires at least: 6.8
 * Requires PHP: 8.2
 *
 * The two headers above are enforced by WordPress at activation time
 * (Requires PHP since 5.2): the Plugins screen refuses to activate the
 * plugin on a server that does not meet them.
 */
defined( 'ABSPATH' ) || exit;

/**
 * Runtime check for the things a header cannot express: extensions,
 * database version, and available memory for a heavy export.
 */
add_action( 'admin_init', static function () {
	global $wpdb;

	$problems = array();

	if ( ! extension_loaded( 'intl' ) ) {
		$problems[] = 'the intl PHP extension';
	}

	// wpdb::db_version() returns the server version string, e.g. "8.0.36".
	if ( version_compare( $wpdb->db_version(), '5.7', '<' ) ) {
		$problems[] = 'MySQL 5.7 or newer';
	}

	if ( ! $problems ) {
		return;
	}

	add_action( 'admin_notices', static function () use ( $problems ) {
		printf(
			'<div><p>Acme Reports needs %s.</p></div>',
			esc_html( implode( ', ', $problems ) )
		);
	} );
} );

When to use it

  • Vetting a host before migrating a client site onto it.
  • Diagnosing large image uploads that fail silently in the Media Library.
  • Writing the server block or vhost for a fresh WordPress deployment.
  • Deciding whether a legacy site on PHP 7.4 can be upgraded in place.

More examples

Raising the limits that actually govern uploads

The exact set of dials behind "the upload failed with no error". Work down the list: WordPress memory constants first, then php.ini upload limits, then execution time — the Media Library reports the smallest of upload_max_filesize and post_max_size as the maximum upload size.

Example · php
<?php
// wp-config.php, above the "stop editing" line.

// Front-end and general request ceiling. WordPress raises memory_limit to
// this value; it never lowers an already-higher php.ini setting.
define( 'WP_MEMORY_LIMIT', '256M' );

// Ceiling used by wp_raise_memory_limit() on admin screens and during image
// processing. Default is 256M, or the php.ini value if that is higher.
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

// The two limits WordPress CANNOT set from PHP live in php.ini or the host
// panel, and they cap the upload size the Media Library will accept:
//   upload_max_filesize = 64M
//   post_max_size       = 64M
//   max_execution_time  = 300

// Optional: give image resizing more headroom than the general admin limit.
add_filter( 'image_memory_limit', static function () {
	return '512M';
} );

nginx: no .htaccess, so you write the rewrite

Reach for this when permalinks work on the homepage but every post gives a 404 on nginx: WordPress writes .htaccess rules Apache reads automatically, and on nginx nobody does that for you. The uploads deny block closes the classic path from an upload vulnerability to remote code execution.

Example · nginx
server {
    listen 443 ssl http2;
    server_name example.com;
    root /var/www/example.com;
    index index.php;

    client_max_body_size 64m;  # Must be >= PHP's upload_max_filesize.

    # The rewrite that makes pretty permalinks work. Everything that is not
    # a real file or directory is handed to index.php, which bootstraps
    # WordPress and lets WP_Rewrite parse the path into query vars.
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 300;
    }

    # Never execute PHP that someone uploaded into the media directory.
    location ~* /wp-content/uploads/.*\.php$ { deny all; }
}

Forcing HTTPS behind a proxy or load balancer

The standard fix for a site behind Cloudflare, an ALB or an nginx TLS terminator that renders half-broken because assets are requested over http. Only trust X-Forwarded-Proto when the proxy is the sole route to the origin, otherwise a client can spoof the header.

Example · php
<?php
// wp-config.php — above the require of wp-settings.php.

// Behind a terminating proxy, PHP sees plain HTTP and is_ssl() returns false,
// so WordPress generates http:// URLs and the browser reports mixed content.
// Trust the forwarding header your proxy actually sets, and no other.
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

define( 'WP_HOME', 'https://example.com' );    // No trailing slash.
define( 'WP_SITEURL', 'https://example.com' );
define( 'FORCE_SSL_ADMIN', true );             // Admin and login over TLS only.

Discussion

  • Be the first to comment on this lesson.