Installing WordPress

The browser installer, a hand-written wp-config.php, the WP-CLI three-command install, and what to use locally.

Syntaxwp core download && wp config create --dbname=<db> --dbuser=<user> --dbpass=<pass> && wp core install --url=<url> --title=<title> --admin_user=<user> --admin_email=<email>

Installing WordPress means three things: files on disk, a database it may write to, and credentials joining the two. Every installation method does exactly that and nothing more.

The five-minute install

Unpack the release, copy the contents of the wordpress directory into your web root, create an empty database and a user with full privileges on it, then load the site in a browser. With no wp-config.php present, wp-load.php redirects you to /wp-admin/setup-config.php, which writes the file for you from wp-config-sample.php. You then land on /wp-admin/install.php to set the site title and the first administrator.

Manual

The same steps, except you copy wp-config-sample.php to wp-config.php yourself, fill in DB_NAME, DB_USER, DB_PASSWORD and DB_HOST, paste fresh salts from the WordPress.org secret-key service, and open /wp-admin/install.php directly. This is the version you script.

WP-CLI

Three commands and no browser: wp core download, wp config create, wp core install. wp config create is simply the shorthand for the manual step — it writes the same file from the same sample and fetches salts unless you pass --skip-salts. wp core install requires --url, --title, --admin_user and --admin_email.

What trips people up

WordPress decides a site is already installed by finding the siteurl option in the database, and it stores that URL absolutely. Restore a dump onto a different domain and you get a login page that bounces you to the old host until siteurl and home are updated.

Example

Example · php
<?php
/**
 * wp-config.php for a manual install — a trimmed copy of wp-config-sample.php.
 * Place it in the WordPress root, then open /wp-admin/install.php.
 */

// ** Database settings ** //
define( 'DB_NAME', 'acme_wp' );
define( 'DB_USER', 'acme_wp' );
define( 'DB_PASSWORD', getenv( 'ACME_DB_PASSWORD' ) ); // Keep secrets out of the repo.
define( 'DB_HOST', '127.0.0.1' );
define( 'DB_CHARSET', 'utf8mb4' ); // The sample still ships 'utf8'; utf8mb4 stores emoji.
define( 'DB_COLLATE', '' );

/**
 * Authentication unique keys and salts — eight of them.
 * Generate real values at https://api.wordpress.org/secret-key/1.1/salt/
 * Changing any of these invalidates every login cookie on the site.
 */
define( 'AUTH_KEY',         'put your unique phrase here' );
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
define( 'NONCE_KEY',        'put your unique phrase here' );
define( 'AUTH_SALT',        'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
define( 'NONCE_SALT',       'put your unique phrase here' );

// A variable, not a constant. Tables are created with this prefix at install
// time; changing it afterwards makes WordPress think the site is uninstalled.
$table_prefix = 'wp_';

define( 'WP_DEBUG', false );

/* That's all, stop editing! Happy publishing. */

if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', __DIR__ . '/' );
}

require_once ABSPATH . 'wp-settings.php';

When to use it

  • Provisioning a fresh WordPress site from a shell script or CI pipeline.
  • Migrating a database dump to a new domain and fixing the redirect loop it causes.
  • Standing up a local copy of a client site to reproduce a bug.
  • Installing WordPress on hosting that only gives you FTP and phpMyAdmin.

More examples

A scripted install with WP-CLI

The install you actually want in a provisioning script or a CI job: repeatable, no browser, and every value explicit. --skip-email stops WordPress mailing the new admin, which matters when you build throwaway environments on every branch.

Example · bash
#!/usr/bin/env bash
set -euo pipefail

DB_NAME=acme_wp
DB_USER=acme_wp
DB_PASS="$(openssl rand -base64 24)"

# 1. Database. WP-CLI can create it for you once the credentials exist.
mysql -u root -e "CREATE DATABASE ${DB_NAME} DEFAULT CHARACTER SET utf8mb4;"
mysql -u root -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
mysql -u root -e "GRANT ALL ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';"

# 2. Core files. --version pins the release; --skip-content omits the bundled
#    themes and plugins, which you usually manage yourself.
wp core download --version=6.8 --locale=en_GB

# 3. wp-config.php. This is the shorthand for hand-editing wp-config-sample.php:
#    same file, same sample, with salts fetched automatically (--skip-salts opts out).
wp config create \
  --dbname="${DB_NAME}" \
  --dbuser="${DB_USER}" \
  --dbpass="${DB_PASS}" \
  --dbhost=127.0.0.1 \
  --dbprefix=acme_ \
  --dbcharset=utf8mb4

# 4. Run the installer. All four of these options are required.
wp core install \
  --url="https://acme.test" \
  --title="Acme" \
  --admin_user="acme_admin" \
  --admin_email="[email protected]" \
  --skip-email

wp option update blogdescription ""
wp rewrite structure '/%postname%/' --hard

The five-minute install by hand

What the 'famous five-minute install' actually consists of, including the file-move mistake that puts every site in a /wordpress/ subdirectory. Useful when you only have FTP or a hosting file manager and no shell for WP-CLI.

Example · bash
# On the server, in the web root.
curl -O https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz

# Move the CONTENTS of the wordpress directory, not the directory itself,
# or your site lives at example.com/wordpress/.
mv wordpress/* wordpress/.htaccess . 2>/dev/null || mv wordpress/* .
rmdir wordpress && rm latest.tar.gz

# Option A: let the browser installer write the config for you.
#   Visit https://example.com/ — with no wp-config.php, wp-load.php redirects
#   to /wp-admin/setup-config.php, which asks for the database details and
#   writes wp-config.php from wp-config-sample.php. Then it hands you on to
#   /wp-admin/install.php.

# Option B: write it yourself, then go straight to the installer.
cp wp-config-sample.php wp-config.php
$EDITOR wp-config.php     # DB_NAME, DB_USER, DB_PASSWORD, DB_HOST
curl -s https://api.wordpress.org/secret-key/1.1/salt/   # paste over the eight placeholders
#   Then visit https://example.com/wp-admin/install.php

# The web server user needs to write wp-content; it does not need to own core.
chown -R www-data:www-data wp-content

Local development

A checked-in dev environment that pins the WordPress and PHP versions so every developer reproduces the same bugs. The config block writes straight into wp-config.php inside the container, which is why the debug constants belong here rather than in a file you might commit by accident.

Example · json
// .wp-env.json — @wordpress/env spins up WordPress plus MySQL in Docker.
// npx wp-env start, then the site is on http://localhost:8888
// (admin / password) and the test instance on :8889.
{
  "core": "WordPress/WordPress#6.8",
  "phpVersion": "8.2",
  "plugins": [ "." ],
  "themes": [ "./vendor/themes/acme" ],
  "config": {
    "WP_DEBUG": true,
    "WP_DEBUG_LOG": true,
    "WP_DEBUG_DISPLAY": false,
    "WP_ENVIRONMENT_TYPE": "local",
    "WP_DEVELOPMENT_MODE": "plugin"
  },
  "mappings": {
    "wp-content/uploads": "./local-uploads"
  }
}

// Alternatives, all equivalent for our purposes: Local (Flywheel), Laragon,
// MAMP, or your own docker-compose with the official wordpress and mysql
// images. All you need is PHP 8.2+, MySQL 8, and a writable directory.

Discussion

  • Be the first to comment on this lesson.