How To Install WordPress on Ubuntu 26.04 LTS

Install WordPress on Ubuntu 26.04

Installing WordPress on Ubuntu 26.04 is straightforward when the server is prepared correctly. The difficult part is not copying WordPress files into /var/www; it is building a reliable hosting environment around them. Database permissions, PHP-FPM, Apache virtual hosts, HTTPS, file ownership, firewall rules, caching, backups, and update procedures all determine whether the site remains stable after launch.

Ubuntu 26.04 LTS provides a modern base for a new WordPress server. Its repositories include PHP 8.5 and MySQL 8.4 LTS, while WordPress currently recommends PHP 8.3 or newer, MySQL 8.0 or newer, or MariaDB 10.6 or newer. HTTPS is also a recommended requirement for every WordPress installation. [16][20]

This guide uses the LAMP architecture:

  • Linux: Ubuntu 26.04 LTS.
  • Web server: Apache 2.4.
  • Database: MySQL 8.4.
  • Application runtime: PHP 8.5 with PHP-FPM.
  • Application: WordPress.
  • TLS: Let’s Encrypt with Certbot.

The procedure works well for a business website, blog, documentation site, WooCommerce store, or small content publishing platform. It also provides a sensible foundation for production deployments, although high-traffic installations will eventually need additional layers such as object caching, a CDN, database tuning, centralized logging, and a separate backup system.

The examples assume:

Domain: example.com
Web root: /var/www/example.com/public
Database: example_wp
Database user: example_wp_user
Server user: deploy

Replace these values with your own domain and usernames. Do not copy passwords from this article into a live server.

Before Installing WordPress

A clean server makes troubleshooting much easier. If Apache, Nginx, an old PHP version, or another control panel is already installed, inspect the environment before changing anything.

Minimum server requirements

For a small WordPress site, a practical starting point is:

  • One or two virtual CPU cores.
  • At least 2 GB of RAM.
  • 25 GB or more of SSD storage.
  • A public IPv4 address.
  • A registered domain name.
  • DNS access for the domain.
  • SSH access with sudo privileges.

The minimum hardware is not the same as a comfortable production configuration. WordPress itself may run on a small VPS, but plugins, image processing, WooCommerce, security scans, backups, and traffic bursts consume additional memory and CPU.

For a personal blog with low traffic, 2 GB of RAM may be sufficient. A WooCommerce store, news site, or website running a visual page builder should generally start with 4 GB or more. Disk performance also matters. A fast NVMe volume reduces the delay caused by PHP file reads, database queries, image manipulation, and backup jobs.

Confirm the Ubuntu release

Connect to the server over SSH:

ssh deploy@SERVER_IP

Check the operating system:

cat /etc/os-release
hostnamectl
uname -a

Confirm that the server reports Ubuntu 26.04. Also check available resources:

free -h
df -hT
nproc

If the root filesystem is nearly full, stop and resolve that issue first. WordPress updates can fail when /tmp, /var, or the root filesystem has insufficient free space.

Set the hostname and time zone

A consistent hostname helps when reading logs and monitoring multiple servers:

sudo hostnamectl set-hostname wp01.example.com

Set the appropriate time zone. For Indonesia, for example:

sudo timedatectl set-timezone Asia/Jakarta

Verify the result:

timedatectl

WordPress stores and displays dates using its own configuration, but accurate system time remains important for TLS certificates, cron jobs, logs, monitoring, and database maintenance.

Update the server

Refresh package metadata and install available security updates:

sudo apt update
sudo apt full-upgrade -y
sudo reboot

Reconnect after the reboot:

ssh deploy@SERVER_IP

Ubuntu 26.04 includes modern PHP and MySQL packages, but updates remain necessary because security fixes continue to arrive after the initial release. Ubuntu security advisories, for example, regularly publish patched PHP package versions for vulnerabilities affecting supported releases. [19]

Configure Basic Server Security

A WordPress website should not be the first security layer on the server. Reduce unnecessary exposure before installing the application.

Create a non-root administrative user

If the VPS provider gave you direct root access, create a normal user:

sudo adduser deploy
sudo usermod -aG sudo deploy

Copy your SSH key to the account from your workstation:

ssh-copy-id deploy@SERVER_IP

Test the new account in a separate terminal before changing SSH settings:

ssh deploy@SERVER_IP
sudo -v

Do not disable root SSH access until you have confirmed that the new account works.

Harden SSH

Open the SSH configuration:

sudoedit /etc/ssh/sshd_config

Use settings similar to these:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

The exact configuration may differ if your provider uses cloud-init or an included configuration file under /etc/ssh/sshd_config.d/. Check the effective settings with:

sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|pubkeyauthentication'

Validate the configuration before restarting SSH:

sudo sshd -t
sudo systemctl reload ssh

Keep your existing SSH session open while testing a new connection. This simple habit prevents many avoidable lockouts.

Configure UFW

Install and enable the uncomplicated firewall:

sudo apt install ufw -y

Allow SSH before enabling the firewall:

sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable

Review the rules:

sudo ufw status verbose

Apache Full allows HTTP and HTTPS. Port 80 is needed initially for normal web traffic and often for Let’s Encrypt HTTP validation. After HTTPS is working, HTTP can remain open so Apache can redirect visitors to HTTPS.

Do not expose MySQL to the public internet unless there is a specific, carefully controlled requirement. The database should listen locally whenever WordPress and MySQL run on the same server.

Install Apache, MySQL, and PHP

Ubuntu’s web service documentation covers both Apache and Nginx as common web servers. This installation uses Apache because its virtual-host workflow and .htaccess compatibility are familiar to WordPress administrators. [18]

Install Apache

sudo apt install apache2 apache2-utils -y

Enable Apache at boot and start it:

sudo systemctl enable --now apache2

Check its status:

sudo systemctl status apache2 --no-pager

Verify the configuration:

sudo apache2ctl configtest

You should see:

Syntax OK

At this point, visit the server IP in a browser. The default Apache page should appear. If it does not, check the firewall, cloud provider security group, DNS, and service logs:

sudo journalctl -u apache2 -n 100 --no-pager

Install MySQL

Install the database server:

sudo apt install mysql-server -y

Enable and start it:

sudo systemctl enable --now mysql

Check the service:

sudo systemctl status mysql --no-pager

Ubuntu 26.04 provides MySQL 8.4 LTS, which is a current long-term-support database release in the Ubuntu 26.04 platform. [16]

Run the MySQL hardening utility:

sudo mysql_secure_installation

The prompts can vary by package version. Review each question carefully. Remove anonymous users, disallow remote root login, remove the test database, and reload privilege tables when prompted.

Ubuntu commonly configures the local MySQL root account for socket authentication. You can access it with:

sudo mysql

Do not use the MySQL root account inside WordPress. Create a separate database and user with permissions limited to that database.

Create the WordPress database

Open the MySQL shell:

sudo mysql

Run the following SQL:

CREATE DATABASE example_wp
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'example_wp_user'@'localhost'
  IDENTIFIED BY 'REPLACE_WITH_A_LONG_RANDOM_PASSWORD';

GRANT ALL PRIVILEGES ON example_wp.* TO 'example_wp_user'@'localhost';

FLUSH PRIVILEGES;
EXIT;

Use a password generated by a password manager. A long random password is better than a complicated phrase reused across multiple services.

Test the credentials:

mysql -u example_wp_user -p -h localhost example_wp

After entering the password, run:

SELECT VERSION();
SHOW TABLES;
EXIT;

The database will initially contain no WordPress tables. The important result is that the user can authenticate and access the database.

Install PHP and WordPress extensions

Install PHP-FPM and the extensions commonly required by WordPress themes, plugins, image processing, XML feeds, REST API clients, and compressed archives:

sudo apt install -y \
  php8.5-fpm \
  php8.5-mysql \
  php8.5-cli \
  php8.5-curl \
  php8.5-gd \
  php8.5-mbstring \
  php8.5-xml \
  php8.5-zip \
  php8.5-intl \
  php8.5-imagick

php8.5-imagick may not be available in every repository configuration. If the package cannot be found, continue without it and install it later if a theme or image optimization workflow requires it.

Enable PHP-FPM:

sudo systemctl enable --now php8.5-fpm

Check the PHP version:

php -v

Confirm PHP-FPM is active:

sudo systemctl status php8.5-fpm --no-pager

Configure Apache to send PHP requests to PHP-FPM:

sudo a2enmod proxy_fcgi setenvif rewrite headers expires ssl
sudo a2enconf php8.5-fpm
sudo systemctl restart apache2

Using PHP-FPM separates the web server from the PHP worker pool and generally gives administrators better control over process limits, memory usage, and application isolation than relying on the traditional Apache PHP module.

Create the WordPress Web Root

A dedicated virtual-host directory is preferable to placing the site directly in /var/www/html. It keeps the website separate from Apache’s default site and makes future migrations easier.

Create the directory:

sudo mkdir -p /var/www/example.com/public

Temporarily give your deployment user ownership of the directory:

sudo chown -R deploy:deploy /var/www/example.com

Set safe default permissions:

sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;

Download WordPress from the official project:

cd /tmp
curl -fLO https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz

Copy the files into the document root:

sudo cp -a /tmp/wordpress/. /var/www/example.com/public/

Check the result:

ls -la /var/www/example.com/public

The directory should contain files such as wp-admin, wp-includes, wp-content, index.php, and wp-config-sample.php.

File ownership strategies

There are two common approaches.

The simpler approach is to let the web server own the WordPress files:

sudo chown -R www-data:www-data /var/www/example.com/public

This allows WordPress to install updates and plugins from the dashboard. It is convenient, but a compromised PHP process may have broad write access to the entire application.

A more restrictive production approach gives ownership to a deployment account and allows the web server to write only where necessary:

sudo chown -R deploy:www-data /var/www/example.com/public
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
sudo chmod -R 775 /var/www/example.com/public/wp-content

This still grants substantial write access to wp-content, because WordPress needs to upload media and manage plugin or theme files. A stricter model disables dashboard-based plugin installation and deploys changes through SSH, Git, WP-CLI, or a CI/CD system.

Do not use chmod -R 777. It hides ownership problems by making every file writable and creates an unnecessary security risk.

Configure the Apache Virtual Host

Create a dedicated Apache site configuration:

sudoedit /etc/apache2/sites-available/example.com.conf

Add:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com

    DocumentRoot /var/www/example.com/public

    <Directory /var/www/example.com/public>
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    DirectoryIndex index.php index.html

    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

AllowOverride All permits WordPress to use its .htaccess rewrite rules for pretty permalinks. If you prefer not to use .htaccess, you can configure rewrites directly in the virtual host, but that requires maintaining the rules manually.

Enable the site and disable the default Apache site:

sudo a2ensite example.com.conf
sudo a2dissite 000-default.conf

Test Apache:

sudo apache2ctl configtest

Reload it:

sudo systemctl reload apache2

Check which virtual hosts are active:

sudo apache2ctl -S

If the wrong site responds, inspect ServerName, ServerAlias, DNS records, and the enabled configuration list under /etc/apache2/sites-enabled/.

Configure DNS Before the Browser Install

Create DNS records pointing to the server:

A     example.com       SERVER_IP
A     www.example.com   SERVER_IP

If you use IPv6, add an AAAA record only when IPv6 is correctly configured and reachable through the firewall. A broken IPv6 record can cause some visitors and crawlers to experience intermittent failures even when IPv4 works normally.

Check DNS resolution:

dig +short example.com
dig +short www.example.com

Or:

getent ahosts example.com

Allow time for DNS changes to propagate. During testing, you can use the local /etc/hosts file, but do not confuse local resolution with publicly working DNS.

Install WordPress Through the Browser

Once Apache, PHP, MySQL, and DNS are functioning, open:

http://example.com

The WordPress setup screen should appear.

Enter:

  • Database Name: example_wp
  • Username: example_wp_user
  • Password: the database password you created.
  • Database Host: localhost
  • Table Prefix: a unique prefix such as wp7x_.

The table prefix is not a replacement for proper security, but avoiding the default wp_ prefix can reduce automated assumptions during opportunistic attacks. Do not use a prefix containing spaces or punctuation.

Install WordPress on Ubuntu 26.04

If WordPress reports that it cannot create wp-config.php, create it manually:

cd /var/www/example.com/public
sudo cp wp-config-sample.php wp-config.php
sudoedit wp-config.php

Set the database values:

define( 'DB_NAME', 'example_wp' );
define( 'DB_USER', 'example_wp_user' );
define( 'DB_PASSWORD', 'REPLACE_WITH_DATABASE_PASSWORD' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );
$table_prefix = 'wp7x_';

Add unique authentication salts. Generate them from the WordPress API:

curl -s https://api.wordpress.org/secret-key/1.1/salt/

Copy the returned definitions into wp-config.php, replacing the placeholder salt section.

Protect the configuration file:

sudo chmod 640 /var/www/example.com/public/wp-config.php
sudo chown deploy:www-data /var/www/example.com/public/wp-config.php

The web server needs to read the file, but it does not need to write it.

If you want to prevent administrators from editing plugins and themes through the dashboard, add:

define( 'DISALLOW_FILE_EDIT', true );

For a controlled deployment workflow, you may also use:

define( 'DISALLOW_FILE_MODS', true );

Do not enable DISALLOW_FILE_MODS until you have another reliable update process, such as WP-CLI, Composer, or deployment automation.

Complete the installer with a site title, administrator username, unique password, and administrative email address. Avoid using admin as the administrator username. The login URL is normally:

https://example.com/wp-admin/

Enable HTTPS with Let’s Encrypt

WordPress should run entirely over HTTPS. TLS protects login credentials, administrator sessions, cookies, contact forms, and API traffic. It also avoids mixed-content problems and is expected by modern browsers and search engines.

Install Certbot and its Apache plugin:

sudo apt install certbot python3-certbot-apache -y

Request certificates:

sudo certbot --apache -d example.com -d www.example.com

Certbot can create the HTTPS virtual host and redirect HTTP traffic to HTTPS. Choose the redirect option when prompted.

Test the renewal timer:

sudo systemctl status certbot.timer --no-pager
sudo certbot renew --dry-run

Inspect the final response:

curl -I http://example.com
curl -I https://example.com

The HTTP request should redirect to HTTPS. Check the canonical host as well. Decide whether the site should use example.com or www.example.com, then keep that choice consistent in WordPress, redirects, canonical URLs, XML sitemaps, and internal links.

In the WordPress dashboard, go to Settings → General and confirm that both WordPress Address (URL) and Site Address (URL) use the correct HTTPS version.

Configure PHP for WordPress

The default PHP configuration is conservative and may be too restrictive for media-heavy websites, WooCommerce, or page builders.

Find the active PHP-FPM configuration:

php --ini

The FPM configuration is usually located under:

/etc/php/8.5/fpm/php.ini

Open it:

sudoedit /etc/php/8.5/fpm/php.ini

Reasonable starting values for a small-to-medium site include:

memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120
max_input_vars = 3000

post_max_size should be equal to or larger than upload_max_filesize. Increasing these values does not solve every upload problem. Apache request limits, reverse proxies, CDN limits, filesystem permissions, and plugin-specific restrictions can also apply.

Restart PHP-FPM after changing the file:

sudo systemctl restart php8.5-fpm

Check for configuration errors:

sudo journalctl -u php8.5-fpm -n 100 --no-pager

Tune PHP-FPM workers

PHP-FPM uses worker processes to execute PHP requests. Too few workers cause requests to queue. Too many create memory pressure and may trigger the OOM killer.

Find the pool configuration:

sudoedit /etc/php/8.5/fpm/pool.d/www.conf

A modest VPS might begin with:

pm = dynamic
pm.max_children = 20
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500

These are starting points, not universal values. Measure memory usage under real traffic. If each PHP worker consumes approximately 80 MB and the server has only 2 GB of RAM, setting pm.max_children = 50 is a quick way to create swapping or an out-of-memory event.

Monitor workers and system pressure:

ps -o pid,ppid,%mem,%cpu,cmd -C php-fpm8.5
free -h
vmstat 1

After making changes:

sudo php-fpm8.5 -t
sudo systemctl restart php8.5-fpm

Improve WordPress Performance

A successful WordPress installation can still feel slow if every page request executes PHP and queries MySQL from scratch.

Enable OPcache

OPcache stores compiled PHP bytecode in memory. Check whether it is loaded:

php -m | grep -i opcache

If necessary, install the package:

sudo apt install php8.5-opcache -y

Review the FPM OPcache settings:

sudoedit /etc/php/8.5/fpm/conf.d/10-opcache.ini

A practical baseline is:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2

For immutable deployments where PHP files are changed only during releases, opcache.validate_timestamps=0 can improve performance. It requires an explicit PHP-FPM restart after every code deployment, so do not use it casually on a dashboard-managed site.

Restart FPM:

sudo systemctl restart php8.5-fpm

Use page caching

For anonymous visitors, full-page caching usually provides a larger improvement than increasing PHP worker counts. A cache plugin can serve generated HTML without executing WordPress on every request.

Exclude or carefully handle:

  • /wp-admin/.
  • /wp-login.php.
  • Cart and checkout pages.
  • Customer account pages.
  • Personalized content.
  • POST requests.
  • Logged-in users.

A cache that accidentally stores a logged-in user’s response can create a serious privacy incident. Test caching with browser developer tools and curl before enabling it broadly.

Add object caching

Persistent object caching reduces repeated database queries. Redis is commonly used for this purpose:

sudo apt install redis-server php8.5-redis -y
sudo systemctl enable --now redis-server

Check that Redis listens locally:

sudo ss -ltnp | grep 6379

Do not expose Redis to the public internet. Bind it to localhost and restrict access through its configuration and firewall.

Install a reputable WordPress Redis object-cache plugin, then verify that cache hits increase and database load decreases. Object caching is not a substitute for fixing an inefficient plugin or a badly written query.

Optimize images and static files

Large images often dominate page weight. Resize images to their display dimensions, use modern formats such as WebP or AVIF when practical, and avoid uploading a 6 MB camera image for a 900-pixel blog thumbnail.

Enable compression and browser caching in Apache:

sudo a2enmod deflate expires headers
sudo systemctl reload apache2

A simple virtual-host addition is:

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/avif "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
</IfModule>

Use long cache lifetimes only when filenames are versioned or cache invalidation is reliable. Otherwise, visitors may continue receiving stale CSS or JavaScript after a deployment.

Use system-level monitoring

Useful commands include:

top
htop
iotop
iostat -xz 1
ss -s
df -h
du -sh /var/www/example.com/*

Install additional tools if needed:

sudo apt install htop iotop sysstat ncdu -y

For production, collect metrics rather than relying only on occasional SSH sessions. Monitor CPU load, RAM, swap activity, disk utilization, inode usage, PHP-FPM saturation, MySQL connections, HTTP response codes, certificate expiry, and backup success.

Secure the WordPress Installation

Security is a continuous operating process, not a plugin checkbox.

Keep the operating system updated

Run regular updates:

sudo apt update
sudo apt full-upgrade -y

Review services enabled at boot:

systemctl list-unit-files --state=enabled

Remove software that is not needed. Every additional daemon increases the attack surface and complicates patch management.

Protect WordPress accounts

Use long, unique passwords and enable multi-factor authentication for administrators. Give editors, authors, and contractors only the roles they need. Remove former users promptly.

Do not install plugins solely because they are popular. Check whether a plugin is actively maintained, compatible with the current WordPress and PHP versions, and necessary for the site’s function. Unused plugins should be deleted rather than merely deactivated.

Restrict XML-RPC when appropriate

Some sites do not use XML-RPC. If it is not required, disable it at the application or web-server layer. Before doing so, confirm that the site does not depend on Jetpack, mobile publishing, or another integration that uses XML-RPC.

For Apache, one option is:

<Files "xmlrpc.php">
    Require all denied
</Files>

Add this inside the relevant <Directory> block or use an appropriate application-level method. Test integrations after making the change.

Add security headers carefully

Headers can improve browser security, but an incorrectly configured Content Security Policy can break WordPress admin screens and third-party services.

A reasonable starting point is:

Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

Do not enable HSTS with a long duration until HTTPS works consistently on every subdomain. HSTS can make recovery more difficult if a forgotten subdomain still serves HTTP.

Protect backups

A backup stored on the same VPS is not a complete backup strategy. If the server is deleted, encrypted, compromised, or suffers storage failure, local backups may disappear with it.

Maintain:

  • Off-server database backups.
  • Off-server uploads and WordPress file backups.
  • Infrastructure or VPS snapshots.
  • Encryption for backup storage.
  • Retention policies.
  • Regular restore tests.

A backup command using mysqldump might look like:

mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  -u example_wp_user \
  -p example_wp > /var/backups/example_wp-$(date +%F).sql

Do not leave database passwords in shell history or unprotected scripts. Use a restricted configuration file, a secret manager, or an appropriate backup utility.

A backup is not proven until a restore succeeds. Test restoring the database and files to a staging server at regular intervals.

Technical SEO After Installation

A clean server helps SEO, but it does not automatically create a technically sound website.

Verify canonical URLs

Choose one canonical hostname:

https://example.com

or:

https://www.example.com

Redirect the alternative hostname to the preferred one. Confirm that WordPress generates the same preferred host in:

  • Canonical link elements.
  • XML sitemaps.
  • Open Graph URLs.
  • Internal links.
  • RSS feeds.
  • Schema markup.

Avoid chains such as HTTP → HTTPS → www → non-www. Use one direct redirect wherever possible.

Configure permalinks

In the WordPress dashboard, open Settings → Permalinks and select a clean structure, commonly:

/%postname%/

Save the settings even if the desired structure is already selected. WordPress may need to write updated rewrite rules.

Test a post URL, category URL, author archive, search URL, and 404 page. If every URL returns the homepage, inspect Apache’s rewrite module and .htaccess permissions.

Test crawlability

Check:

curl -I https://example.com/
curl -I https://example.com/robots.txt
curl -I https://example.com/wp-sitemap.xml
curl -I https://example.com/nonexistent-page

A missing page should return HTTP 404, not HTTP 200 with a homepage response. Soft 404s can waste crawl resources and confuse search engines.

Check response timing:

curl -s -o /dev/null -w \
'HTTP %{http_code}\nDNS %{time_namelookup}s\nConnect %{time_connect}s\nTTFB %{time_starttransfer}s\nTotal %{time_total}s\n' \
https://example.com/

This does not replace browser-based Core Web Vitals testing, but it quickly identifies slow DNS, connection, or server response behavior.

Avoid indexing staging environments

If a staging copy is created, protect it with authentication or network controls. A noindex directive is useful but should not be the only barrier when the staging site contains private content or customer data.

Troubleshooting Common Errors

Apache returns 403 Forbidden

Check the web root permissions:

namei -l /var/www/example.com/public
sudo -u www-data test -r /var/www/example.com/public/index.php

Review the Apache error log:

sudo tail -f /var/log/apache2/example.com-error.log

Common causes include a directory without execute permission, an incorrect <Directory> path, restrictive ownership, or an Apache rule denying access.

PHP files download instead of executing

Confirm PHP-FPM is active:

systemctl status php8.5-fpm

Check enabled Apache configuration:

apache2ctl -M | grep -E 'proxy_fcgi|setenvif'
ls -l /etc/apache2/conf-enabled/

Enable the FPM configuration again if necessary:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.5-fpm
sudo systemctl restart apache2

Never leave a PHP test file containing phpinfo() on a public production server. Remove it after testing.

WordPress cannot connect to the database

Verify that MySQL is running:

sudo systemctl status mysql

Test the exact credentials:

mysql -u example_wp_user -p -h localhost example_wp

Check wp-config.php for hidden spaces, incorrect quotes, a wrong database name, or a mismatched password. Confirm the user host is localhost, not an unexpected remote host.

Inspect MySQL logs:

sudo journalctl -u mysql -n 100 --no-pager

Pretty permalinks return 404

Enable rewrite support:

sudo a2enmod rewrite
sudo systemctl reload apache2

Confirm the virtual host contains:

AllowOverride All

Then resave Settings → Permalinks in WordPress. Check that .htaccess exists and is readable:

ls -la /var/www/example.com/public/.htaccess

If Apache logs show that overrides are not allowed, the <Directory> block is probably pointing to the wrong path.

“The uploaded file exceeds the upload_max_filesize directive”

Check both PHP values:

php -i | grep -E 'upload_max_filesize|post_max_size'

Remember that command-line PHP and PHP-FPM can use different configuration files. Create a temporary diagnostic file only when necessary, inspect the browser result, and delete it immediately.

After editing /etc/php/8.5/fpm/php.ini:

sudo systemctl restart php8.5-fpm

502 Bad Gateway or intermittent PHP failures

Check the FPM socket and logs:

sudo systemctl status php8.5-fpm
sudo journalctl -u php8.5-fpm -n 100 --no-pager
sudo tail -f /var/log/apache2/example.com-error.log

A 502 can result from a stopped FPM service, a wrong socket path, exhausted workers, or a process killed by the kernel. Check memory pressure:

dmesg -T | grep -i -E 'killed process|out of memory|oom'
free -h

Do not simply increase pm.max_children. First determine whether the server has enough RAM.

Let’s Encrypt validation fails

Check that DNS points to the correct server:

dig +short example.com

Confirm port 80 is reachable:

sudo ufw status
sudo ss -ltnp | grep ':80'

Cloud firewall rules, CDN proxy settings, incorrect IPv6 records, and an existing redirect configuration are common causes. If a CDN is in front of the server, use a validation method compatible with that architecture.

Site is slow after launch

Separate the problem into layers:

  • Check server response time with curl.
  • Review Apache access logs for slow or repeated requests.
  • Inspect PHP-FPM worker usage.
  • Check MySQL activity and slow queries.
  • Disable recently installed plugins one at a time.
  • Test with page cache enabled and disabled.
  • Check whether bots are requesting expensive search, feed, or login endpoints.
  • Inspect disk latency with iostat.

A large number of requests to wp-login.php, xmlrpc.php, or expensive search URLs can consume PHP workers even when legitimate traffic is modest. Rate limiting, caching, WAF rules, and application changes may all be needed.

r00t is a Linux Systems Administrator and open-source advocate with over ten years of hands-on experience in server infrastructure, system hardening, and performance tuning. Having worked across distributions such as Debian, Arch, RHEL, and Ubuntu, he brings real-world depth to every article published on this blog. r00t writes to bridge the gap between complex sysadmin concepts and practical, everyday application — whether you are configuring your first server or optimizing a production environment. Based in New York, US, he is a firm believer that knowledge, like open-source software, is best when shared freely.

Related Posts