Shared hosting works until it doesn’t — slow page loads, resource limits, no control over PHP versions, and the moment your site gets a traffic spike it either dies or you’re hit with an upgrade bill.
A VPS gives you a dedicated environment for WordPress that you control entirely. You choose the PHP version, configure Apache exactly as you want, tune MySQL for your workload, and no other tenant can affect your performance. And with a free VPS, you pay nothing to start.
This guide is specifically Apache + MySQL — if you want Nginx instead, we have a separate guide on installing WordPress with Nginx on a free VPS. Apache is the better choice for WordPress when you need .htaccess support, which most WordPress plugins and permalink structures depend on.
Why Host WordPress on a VPS Instead of Shared Hosting
| Factor | Shared Hosting | Free VPS (VPSWala) |
|---|---|---|
| PHP version control | Limited — provider decides | ✅ Install any version you want |
| MySQL access | phpMyAdmin only, restricted | ✅ Full root MySQL access |
| Performance consistency | ❌ Shared resources — noisy neighbours | ✅ Dedicated CPU and RAM |
| OPcache control | ❌ Usually disabled or limited | ✅ Full OPcache configuration |
| Apache/Nginx config | ❌ No access | ✅ Full server config control |
| Multiple sites | Limited by plan | ✅ Unlimited via Apache virtual hosts |
| Cost | $3–15/month | ✅ Free forever (Starter) or 30-day trial |
The performance difference between shared hosting and a VPS is immediate and measurable. On shared hosting, every PHP request waits in line behind other tenants’ traffic. On a VPS, Apache serves your WordPress site with dedicated resources and no queue.
What You Need Before You Start
- A VPS running Ubuntu 22.04 or 24.04 — minimum 1 vCPU, 1 GB RAM
- Root or sudo SSH access
- A domain name — pointed to your VPS IP address (required for SSL in Step 9)
- About 20 minutes
DNS setup first: Before starting, add an A record for your domain pointing to your VPS IP. DNS propagation takes up to 48 hours — if you start Step 9 before DNS has propagated, Let’s Encrypt will fail. You can test everything else using your VPS IP while waiting for DNS.
Step 1 — Get a Free VPS and Connect
If you don’t have a VPS yet, VPSWala’s free VPS Server deploys Ubuntu 22.04 or 24.04 in 60 seconds — no credit card required. The Starter plan (ARM64, 2 GB RAM) handles a personal WordPress site or low-traffic blog comfortably. The 30-day Professional free trial (8-core AMD EPYC, 8 GB DDR5 ECC RAM) handles 10–20 WordPress sites without breaking a sweat.
Connect via SSH once deployed:
ssh root@YOUR_VPS_IP
If you’re connecting for the first time and want to set up SSH key authentication for security, our SSH tips guide covers it in detail.
Step 2 — Update and Prepare the Server
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget unzip
Takes 1–2 minutes. Never skip on a fresh VPS — you may otherwise hit dependency conflicts when installing Apache or PHP later.
After your first login on a new VPS, check our list of 5 things you must do after launching a Linux VPS — covers creating a sudo user, disabling root SSH login, and other baseline hardening that applies before any service installs.
Step 3 — Install Apache Web Server
sudo apt install -y apache2
sudo systemctl start apache2
sudo systemctl enable apache2
Check Apache is running:
sudo systemctl status apache2
Should show active (running). Now open the firewall:
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable
sudo ufw status
Visit http://YOUR_VPS_IP in a browser — you should see the Ubuntu Apache default page. That confirms Apache is installed and publicly reachable.
Cloud firewall note: If you can’t reach Apache in the browser despite the above showing correctly, check your VPS provider’s external firewall. Most providers (including VPSWala via Proxmox) have a separate network-level firewall — ports 80 and 443 need to be open there too, not just in UFW. This is the most common reason Apache appears running but can’t be reached from the internet. See our guide on opening ports 80 and 443 for the full walkthrough.
Step 4 — Install MySQL and Create WordPress Database
sudo apt install -y mysql-server
sudo systemctl start mysql
sudo systemctl enable mysql
Run the security wizard:
sudo mysql_secure_installation
- Validate password plugin:
Y - Password strength:
1(MEDIUM) or2(STRONG) - Remove anonymous users:
Y - Disallow remote root login:
Y - Remove test database:
Y - Reload privilege tables:
Y
Create the WordPress database and a dedicated user — never connect WordPress directly with the MySQL root account:
sudo mysql -u root
CREATE DATABASE wordpress_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'YourStrongPassword123!';
GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wordpress_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Write down the database name, username, and password — you’ll need them in Step 6.
utf8mb4 charset when creating your database — it supports the full Unicode range including emojis. The older utf8 charset in MySQL is actually UTF-8 limited to 3 bytes and will cause silent data corruption when users post emoji content.Step 5 — Install PHP 8.3 and WordPress Extensions
Ubuntu 24.04 ships PHP 8.3 natively. For Ubuntu 22.04, add the Ondřej PPA first:
# Ubuntu 22.04 ONLY
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
Install PHP and every extension WordPress needs or recommends:
sudo apt install -y php8.3 libapache2-mod-php8.3 php8.3-mysql \
php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip \
php8.3-intl php8.3-opcache php8.3-bcmath php8.3-imagick \
php8.3-soap php8.3-cli
Enable PHP in Apache and restart:
sudo a2enmod php8.3
sudo systemctl restart apache2
Verify PHP version:
php -v
Check all WordPress-required extensions are installed:
php -m | grep -E 'curl|gd|mbstring|xml|zip|intl|opcache|imagick|mysql'
Every extension you installed should appear in that list. If any are missing, install them individually: sudo apt install php8.3-MISSING_EXTENSION.
Step 6 — Download and Configure WordPress
Create the directory for your site:
sudo mkdir -p /var/www/yourdomain.com
cd /var/www/yourdomain.com
Download the latest WordPress directly from wordpress.org:
sudo wget https://wordpress.org/latest.tar.gz
sudo tar -xzf latest.tar.gz
sudo mv wordpress/* .
sudo rm -rf wordpress latest.tar.gz
Create and configure wp-config.php:
sudo cp wp-config-sample.php wp-config.php
sudo nano wp-config.php
Update the database credentials block:
define( 'DB_NAME', 'wordpress_db' );
define( 'DB_USER', 'wordpress_user' );
define( 'DB_PASSWORD', 'YourStrongPassword123!' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );
Replace the security keys section by visiting https://api.wordpress.org/secret-key/1.1/salt/, copying the generated output, and replacing the matching block in wp-config.php. This is a security requirement — don’t skip it.
While you have wp-config.php open, add these two lines anywhere before the line that says /* That's all, stop editing! */:
define( 'WP_DEBUG', false );
define( 'FS_METHOD', 'direct' );
FS_METHOD = direct tells WordPress to write files directly without FTP credentials — required when Apache runs as www-data and you’ve set file ownership correctly in the next step.
Step 7 — Configure Apache Virtual Host for WordPress
Enable the Apache rewrite module — WordPress permalinks and most plugins depend on it:
sudo a2enmod rewrite
Create the virtual host configuration file:
sudo nano /etc/apache2/sites-available/yourdomain.com.conf
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain.com
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
<Directory /var/www/yourdomain.com>
AllowOverride All
Require all granted
Options -Indexes +FollowSymLinks
</Directory>
# WordPress security: block access to sensitive files
<FilesMatch "^(wp-config\.php|\.htaccess|readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
# Block direct access to PHP files in uploads
<Directory /var/www/yourdomain.com/wp-content/uploads>
php_admin_flag engine Off
</Directory>
</VirtualHost>
The security blocks at the bottom are WordPress-specific hardening — they prevent direct access to wp-config.php from the web and block PHP execution in the uploads directory (a common attack vector for uploaded malicious files).
Enable the site, disable the default site, test the config, and reload:
sudo a2ensite yourdomain.com.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
apache2ctl configtest must return Syntax OK before you reload. If it returns errors, fix them before proceeding.
Step 8 — Set Correct File Permissions
WordPress file permissions are the single most common source of both security vulnerabilities and “unable to update” errors. The correct setup:
# Set ownership to www-data (Apache's user)
sudo chown -R www-data:www-data /var/www/yourdomain.com
# Directories: 755 — owner can write, others can read/execute
sudo find /var/www/yourdomain.com -type d -exec chmod 755 {} \;
# Files: 644 — owner can write, others can read
sudo find /var/www/yourdomain.com -type f -exec chmod 644 {} \;
# wp-config.php: 640 — only owner and group can read, no world access
sudo chmod 640 /var/www/yourdomain.com/wp-config.php
What this means in practice:
- Apache can write to files — plugin installs, theme updates, media uploads all work without FTP
- Nobody else on the server can read your wp-config.php credentials
- No world-writable files — prevents attackers from modifying your site files even if they find a PHP injection vulnerability
Step 9 — Add Free SSL with Let’s Encrypt
Google has marked HTTP sites as “Not Secure” since 2018. SSL is not optional for any site you care about ranking. Let’s Encrypt makes it free and automatic:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Certbot prompts:
- Email address — for expiry notifications
- Agree to terms —
A - Share email with EFF — your choice
- Redirect HTTP to HTTPS — choose 2 — forces all traffic to HTTPS
After Certbot finishes, it automatically updates your Apache virtual host with the SSL configuration and sets up a cron job for auto-renewal. Test renewal:
sudo certbot renew --dry-run
Verify your site is now serving HTTPS by visiting https://yourdomain.com. You should see a padlock in the browser address bar. If you still see HTTP, check that Apache is reloaded: sudo systemctl reload apache2.
Step 10 — Complete the WordPress Installation
Visit https://yourdomain.com in your browser. WordPress will detect that it hasn’t been installed and launch the installation wizard:
- Select your language
- Enter site title
- Create admin username and a strong password
- Enter your email address
- Decide whether search engines should index the site — uncheck if you’re still building it
- Click Install WordPress
Log into the dashboard at https://yourdomain.com/wp-admin.
First Things to Do After Installing
- Settings → Permalinks: Set to “Post name” (
/%postname%/). This enables clean URLs and is required for most plugins and themes. Click Save Changes to write the.htaccessrewrite rules - Settings → General: Confirm WordPress Address and Site Address both use
https:// - Appearance → Themes: Install a theme
- Plugins → Add New: Install your essential plugins — see our list of 16 essential WordPress plugins
- Dashboard → Updates: Run all available updates
Step 11 — Performance Tuning
1. Configure OPcache
OPcache caches compiled PHP bytecode in memory so PHP doesn’t re-parse your WordPress files on every request. It’s installed — it just needs tuning:
sudo nano /etc/php/8.3/apache2/php.ini
Find and update these values (use Ctrl+W to search):
; OPcache
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
; Upload limits for media
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
max_input_vars = 3000
sudo systemctl restart apache2
2. Enable Apache Compression
sudo a2enmod deflate expires headers
sudo systemctl reload apache2
Add browser caching headers to your WordPress .htaccess file (above the WordPress rules):
sudo nano /var/www/yourdomain.com/.htaccess
# Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
# Gzip compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
# BEGIN WordPress
# (WordPress adds its rules below here automatically)
3. Install a WordPress Caching Plugin
Caching plugins serve pre-built HTML files directly — skipping PHP and MySQL entirely on cached page loads. This is the single highest-impact performance improvement you can make:
- W3 Total Cache — most configurable, works well with Apache
- WP Super Cache — simpler, generates static HTML files that Apache serves directly
- LiteSpeed Cache — best performance but requires LiteSpeed server (not Apache)
For Apache on VPS, install WP Super Cache from Plugins → Add New. Enable it and set cache type to “Simple”. Your page load times should drop significantly on repeat visits.
Performance Summary
| Optimization | Impact on WordPress | What to do |
|---|---|---|
| OPcache | Very high — 30–50% PHP speedup | Configured in Step 11 above |
| Page caching plugin | Very high — skips PHP/MySQL entirely | Install WP Super Cache or W3 Total Cache |
| Gzip compression | High — 60–80% smaller HTML/CSS/JS | mod_deflate enabled above |
| Browser caching | Medium — faster repeat visits | mod_expires configured in .htaccess above |
| Image optimization | High — images are usually 60–80% of page weight | Install Smush or ShortPixel plugin |
| CDN (Cloudflare free) | High for global visitors | Proxy your domain through Cloudflare free plan |
Step 12 — Security Hardening
1. Change Default WordPress Login URL
Every WordPress site on the internet gets hammered by bots attempting to log in at /wp-login.php. Install the WPS Hide Login plugin and change your login URL to something only you know. Drops brute force attempts to near zero. For more on preventing brute force attacks, read our guide on how to prevent brute force attacks in a few steps.
2. Disable XML-RPC if Not Needed
XML-RPC is used for remote publishing tools and the Jetpack plugin. If you don’t use either, disable it — it’s a common DDoS amplification vector:
Add to your .htaccess:
# Disable XML-RPC
<Files xmlrpc.php>
Require all denied
</Files>
3. Block WordPress Enumeration
By default WordPress exposes usernames at /?author=1. Block it by adding to .htaccess:
# Block author enumeration
RewriteCond %{QUERY_STRING} ^author=\d
RewriteRule ^ /? [L,R=301]
4. Set Up Automatic Backups
Install the UpdraftPlus plugin — free tier supports scheduled backups to Google Drive, Dropbox, or your VPS itself. Configure daily backups of both files and database. Also set up server-level automated backups via our guide on backing up and restoring VPS data.
5. Install a Security Plugin
Install Wordfence Security — free tier includes malware scanner, firewall, and login security. Run a full scan after installation and fix any flagged issues. Also see our guide on securing and firewalling your VPS server for server-level hardening beyond WordPress.
Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
| WordPress shows raw PHP code | PHP module not loaded in Apache | sudo a2enmod php8.3 && sudo systemctl restart apache2 |
| 404 on all pages except homepage | mod_rewrite disabled or AllowOverride not set | Run sudo a2enmod rewrite, ensure AllowOverride All is in virtual host config, then go to Settings → Permalinks → Save Changes |
| Error establishing database connection | Wrong credentials in wp-config.php | Double-check DB_NAME, DB_USER, DB_PASSWORD, DB_HOST in wp-config.php match exactly what you created in Step 4 |
| Can’t upload images — too large | PHP upload limits too low | Set upload_max_filesize = 64M and post_max_size = 64M in php.ini, restart Apache |
| Can’t install or update plugins | Wrong file ownership or FS_METHOD not set | Run sudo chown -R www-data:www-data /var/www/yourdomain.com and add define('FS_METHOD','direct') to wp-config.php |
| White screen of death | PHP fatal error or memory limit | Check sudo tail -50 /var/log/apache2/error.log. Temporarily set WP_DEBUG to true in wp-config.php to see the exact error |
| SSL certificate fails | DNS not propagated or port 443 blocked | Run dig yourdomain.com +short — must return your VPS IP. Check port 443 is open in both UFW and your cloud firewall |
| Site loads over HTTP despite SSL | WordPress URL settings still HTTP | Go to Settings → General → change WordPress Address and Site Address to https:// |
| Mixed content warnings (padlock broken) | Some resources still loading over HTTP | Install Really Simple SSL plugin — it fixes mixed content automatically |
Your WordPress site is running on a server you fully control. Apache handles incoming requests, MySQL stores your content, PHP 8.3 with OPcache processes it fast, and Let’s Encrypt keeps your SSL certificate current automatically.
The VPSWala Professional free trial — 8-core AMD EPYC, 8 GB DDR5 ECC RAM, 1 TB Micron NVMe — comfortably handles 10–20 WordPress sites simultaneously. That’s performance that rivals $30–50/month plans at other providers, free for 30 days with no credit card required.
If you prefer Nginx over Apache for your WordPress stack, see our guide on installing WordPress with Nginx on a free VPS. For setting up multiple WordPress sites on the same VPS, check our posts on subdomains with Let’s Encrypt and running multiple VPS instances. Need a Windows RDP server instead? grab a free RDP server here.

