Installing WordPress directly on Ubuntu with Apache and MariaDB is the clean route when the server needs a normal virtual host, predictable file ownership, and an upstream WordPress layout that does not depend on distro repackaging. That keeps upgrades, troubleshooting, and later hardening work aligned with the structure that current WordPress releases expect.
On Ubuntu, Apache serves the site, PHP runs inside the web server through mod_php, and MariaDB stores posts, users, settings, and plugin data. The wp-config.php file joins the web root to the database credentials, while AllowOverride All and mod_rewrite let WordPress manage the .htaccess rules behind clean permalinks.
Examples use a dedicated document root under /var/www, an empty database, and current Ubuntu packages instead of a bundled control-panel stack. The browser installer should be opened only after DNS or a local host mapping points the target hostname at the server, because the most common first-run failures are wrong database credentials, a virtual-host mismatch, or leftover WordPress tables in a reused database.
$ sudo apt-get update $ sudo apt-get install --yes apache2 mariadb-server php libapache2-mod-php php-mysql php-xml php-curl php-zip php-gd php-intl php-mbstring ca-certificates curl unzip rsync Reading package lists... Done Building dependency tree... Done ##### snipped #####
$ sudo systemctl enable --now apache2 mariadb $ systemctl is-active apache2 mariadb active active
The default unit names on Ubuntu are apache2 and mariadb.
$ sudo mariadb MariaDB [(none)]> CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; MariaDB [(none)]> CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'strong_password_here'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost'; MariaDB [(none)]> FLUSH PRIVILEGES; MariaDB [(none)]> EXIT;
Ubuntu commonly authenticates the local MariaDB root account through the Unix socket, so sudo mariadb is usually the correct entry point.
Replace strong_password_here with a unique password that is not reused for any other service account.
$ sudo mkdir -p /var/www/example.com/public_html
$ cd /tmp $ curl -fsSLO https://wordpress.org/latest.tar.gz $ tar -xzf latest.tar.gz
$ sudo rsync -a /tmp/wordpress/ /var/www/example.com/public_html/
The trailing slashes copy the contents of the extracted wordpress directory into the document root instead of nesting another wordpress directory below it.
$ sudo cp /var/www/example.com/public_html/wp-config-sample.php /var/www/example.com/public_html/wp-config.php $ sudo vi /var/www/example.com/public_html/wp-config.php
define( 'DB_NAME', 'wordpress' ); define( 'DB_USER', 'wordpress' ); define( 'DB_PASSWORD', 'strong_password_here' ); define( 'DB_HOST', 'localhost' );
Keep DB_HOST as localhost when MariaDB runs on the same host.
$ curl -s https://api.wordpress.org/secret-key/1.1/salt/
Paste the returned define(… ) lines over the default authentication-key section before saving the file.
$ sudo chown -R www-data:www-data /var/www/example.com/public_html
$ sudo find /var/www/example.com/public_html -type d -exec chmod 755 {} \\;
$ sudo find /var/www/example.com/public_html -type f -exec chmod 644 {} \\;
$ sudo chmod 640 /var/www/example.com/public_html/wp-config.php
This 755/644 pattern keeps the tree readable without leaving the site world-writable.
$ sudo vi /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
Replace example.com and www.example.com with the real hostname for the site before enabling the virtual host.
$ sudo a2enmod rewrite $ sudo a2ensite example.com.conf $ sudo a2dissite 000-default.conf $ sudo apache2ctl configtest Syntax OK
WordPress permalinks depend on mod_rewrite when Apache is serving the site directly.
Related: How to enable or disable Apache modules
Related: How to test Apache configuration
$ sudo systemctl reload apache2
$ curl -I http://www.example.com/wp-admin/install.php HTTP/1.1 200 OK ##### snipped #####
If this check fails on a local-only build, confirm that the hostname resolves to the server in /etc/hosts before troubleshooting Apache or PHP.
If the page reports Already Installed instead of showing the first-run form, the target database already contains WordPress tables. Use an empty database or remove the old tables before retrying.