
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
sudoprivileges.
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.

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.