Your hosting company charges $25-50/month for 2GB of shared RAM. Oracle Cloud gives you 24GB for free. Here is how to make the switch.
This is not a theoretical exercise. I run production sites on Oracle Cloud's Always Free tier. The servers have been up for years. The bill has been $0 every month. And the performance destroys what most managed hosting companies deliver at 10x the price.
In this guide, you will learn exactly why self-hosting WordPress on Oracle Cloud makes financial and technical sense, and then walk through every step of setting it up. By the end, you will have a fully configured WordPress site running on a server with more RAM than most dedicated hosting plans, with HTTPS, security hardening, and performance optimization included.
The Managed Hosting Problem
The WordPress hosting industry is built on a pricing model that depends on you not asking questions. Here is how it works.
The Introductory Pricing Trap
Every major hosting company leads with an impossibly low price. SiteGround advertises $2.99/month. Bluehost shows $2.95/month. Hostinger leads with $1.99/month.
What they do not advertise as prominently: those prices require 36-month commitments paid upfront, and when your term ends, the renewal price jumps 200-400%.
That $2.99/month SiteGround plan renews at $17.99/month. Bluehost's $2.95 becomes $11.99. You locked in a cheap first year, and now you are paying premium prices for the same shared server you started on.
The Feature Upsell Machine
Managed hosting companies have turned basic server features into premium add-ons:
- SSL certificates: $10-15/month. Let's Encrypt provides these for free on any server.
- Automatic backups: $2-5/month. A cron job and a shell script do the same thing.
- Staging environments: $10-20/month. A second directory on your server costs nothing.
- CDN integration: $5-10/month. Cloudflare's free tier handles this.
- Email hosting: $5/month per mailbox. Zoho offers free business email.
Add these up and your "affordable" $3/month hosting plan quietly becomes $30-50/month.
The Shared Resource Problem
The most fundamental issue with managed hosting is what you are actually buying. On shared hosting, your site lives on a server with hundreds of other sites. That 2GB of RAM the plan advertises? You share it. When your neighbor's site gets a traffic spike, your site slows down. When the server runs a backup cycle, everyone's performance drops.
You are paying premium prices for resources you do not exclusively own.
The Control Deficit
Managed hosting restricts what you can do on "your" server:
- Cannot install custom PHP extensions
- Cannot modify server configuration files
- Cannot run custom cron jobs at the system level
- Cannot install certain plugins (some hosts blacklist security or caching plugins that compete with their upsells)
- Cannot access server logs for debugging
- Cannot optimize MySQL configuration
You are renting a walled garden and paying for the privilege of being locked in.
Cost Comparison: Managed Hosting vs Self-Hosting
Let us put real numbers on this. Here is what popular WordPress hosting options actually cost over time, compared to Oracle Cloud's free tier.
Year 1 Costs (Introductory Pricing)
| Provider | Plan | Monthly | Annual | RAM | Storage | SSL | Backups |
|---|---|---|---|---|---|---|---|
| SiteGround | StartUp | $2.99 | $36 | 1 GB (shared) | 10 GB | Free | $24/yr extra |
| Bluehost | Basic | $2.95 | $35 | 2 GB (shared) | 50 GB | Free | $36/yr extra |
| WP Engine | Startup | $20 | $240 | 2 GB (shared) | 10 GB | Free | Included |
| Cloudways | DigitalOcean 2GB | $14 | $168 | 2 GB (dedicated) | 50 GB | Free | $12/yr extra |
| Kinsta | Starter | $30 | $360 | 2 GB (shared) | 10 GB | Free | Included |
| Oracle Cloud | Always Free ARM | $0 | $0 | 24 GB (dedicated) | 200 GB | Free (Let's Encrypt) | Free (you set up) |
Year 2-3 Costs (Renewal Pricing)
This is where the real cost becomes visible. Introductory pricing is gone. You are now paying what the hosting actually costs.
| Provider | Monthly (Renewal) | Year 2 | Year 3 | 3-Year Total |
|---|---|---|---|---|
| SiteGround | $17.99 | $216 | $216 | $468 |
| Bluehost | $11.99 | $144 | $144 | $323 |
| WP Engine | $25 | $300 | $300 | $840 |
| Cloudways | $14 | $168 | $168 | $504 |
| Kinsta | $35 | $420 | $420 | $1,200 |
| Oracle Cloud | $0 | $0 | $0 | $0 |
The only cost of self-hosting on Oracle Cloud is your domain name: $10-15/year. Over three years, that is $30-45 total versus $323-1,200 for managed hosting.
What You Get for $0 vs $25-50/Month
| Resource | Managed Hosting ($25-50/mo) | Oracle Cloud Free Tier ($0/mo) |
|---|---|---|
| RAM | 1-4 GB shared | 24 GB dedicated |
| CPU | Shared (unknown allocation) | 4 OCPUs dedicated |
| Storage | 10-50 GB | 200 GB |
| Root Access | No | Yes |
| Custom PHP Config | No | Yes |
| Server-Level Caching | Limited | Full control |
| Custom NGINX Config | No | Yes |
| Firewall Control | No | Yes |
| SSH Access | Limited or none | Full |
| Automatic Scaling | No (pay to upgrade) | Manual (but 24GB is already massive) |
The numbers speak for themselves. You get more resources for free than most people pay $50/month to access.
Oracle Cloud Free Tier Explained
Oracle Cloud Infrastructure (OCI) launched its Always Free tier in September 2019. Unlike AWS Free Tier (12 months) or Google Cloud Free Tier (limited credits that expire), Oracle's Always Free resources do not expire. Ever.
What You Actually Get
The Always Free tier includes:
Compute (ARM-based Ampere A1):
- Up to 4 OCPUs (Oracle CPUs, roughly equivalent to 4 vCPUs)
- Up to 24 GB RAM
- These resources can be split across multiple instances or used in a single instance
Storage:
- 200 GB total block volume storage
- 2 block volumes
- 10 GB object storage
- 10 GB archive storage
Networking:
- 10 TB outbound data transfer per month
- 1 public IP address per instance
- Virtual Cloud Network (VCN) with subnets, route tables, and security lists
Additional Services:
- Oracle Autonomous Database (2 instances, 20 GB each)
- Load Balancer (1 instance, 10 Mbps)
- Monitoring and Notifications
For WordPress, the compute and storage are what matter. A single ARM instance with 24 GB RAM and 200 GB storage is more than enough to run multiple WordPress sites.
ARM vs x86: Does It Matter?
Oracle's Always Free compute uses ARM-based Ampere A1 processors, not x86 (Intel/AMD). For WordPress, this makes zero practical difference:
- PHP runs natively on ARM
- MySQL/MariaDB runs natively on ARM
- NGINX runs natively on ARM
- Ubuntu has full ARM support
- All major WordPress plugins are PHP-based and CPU-architecture agnostic
ARM processors are actually more power-efficient, meaning Oracle can offer more resources for free. The A1 instances deliver excellent single-thread performance, which is exactly what WordPress needs for page rendering.
The only potential issue: if you use a WordPress plugin that relies on a compiled binary specifically built for x86. This is extremely rare. In practice, I have never encountered a WordPress plugin that does not work on ARM.
How to Stay on the Free Tier
Oracle's billing model separates Always Free resources from paid resources. To ensure you are never charged:
- Only use Always Free eligible shapes when creating instances (the Ampere A1 Flex shape with up to 4 OCPUs and 24 GB RAM)
- Do not exceed the free storage limit (200 GB block volume total)
- Do not enable paid services (Oracle will clearly label what is Always Free vs paid)
- Monitor your usage in the OCI console dashboard
Oracle requires a credit card at sign-up for identity verification. But Always Free resources cannot generate charges. If you accidentally provision a paid resource, Oracle sends warnings before billing, and you can terminate the resource immediately.
The Sign-Up Availability Issue
One thing to know upfront: Oracle Cloud free tier account creation is subject to capacity in your selected home region. During high-demand periods, you may get an error saying the region is full. If this happens:
- Try a different home region (Ashburn, Phoenix, and US-based regions tend to have good availability)
- Try again in a few days
- Try during off-peak hours (early morning or late night)
Once your account is created and your instance is launched, it is yours permanently. The availability limitation only affects new account creation, not existing instances.
Performance Comparison: Why 24 GB Dedicated RAM Changes Everything
Most WordPress performance discussions focus on caching plugins and CDNs. Those matter. But the single biggest factor in WordPress performance is available memory, and whether that memory is shared or dedicated.
How WordPress Uses Memory
When a visitor loads a WordPress page, here is what happens on the server:
- NGINX receives the request and routes it to PHP-FPM
- PHP-FPM spawns a worker process (each worker uses 30-60 MB of RAM)
- WordPress core loads (~15 MB)
- Active plugins load (varies: 5-50 MB depending on plugins)
- Theme renders the page (5-20 MB)
- MySQL executes database queries (each connection uses 10-50 MB)
- The rendered HTML is sent back to the visitor
A single WordPress page load can consume 50-150 MB of RAM. On shared hosting with 1-2 GB total (shared among hundreds of sites), your site is already fighting for resources before the first visitor arrives.
Shared Hosting: The Bottleneck
On a shared hosting plan with 2 GB RAM (shared):
- PHP-FPM can handle approximately 5-10 concurrent workers
- More than 10 simultaneous visitors and your site starts queuing requests
- Database queries compete with other sites on the same MySQL instance
- During peak traffic, response times spike from 200ms to 2-5 seconds
- Your hosting provider may throttle your site if it uses "too many" resources
Oracle Cloud Free Tier: The Difference
On Oracle Cloud with 24 GB dedicated RAM:
- PHP-FPM can handle 100+ concurrent workers
- MySQL gets dedicated memory for query caching and buffer pools
- OPcache can store your entire WordPress codebase in memory
- NGINX FastCGI cache can hold thousands of cached pages in RAM
- Redis object cache can store the entire WordPress object cache in memory
- Simultaneous visitors measured in hundreds, not tens
Real-World PageSpeed Impact
The performance difference is measurable. A standard WordPress site with a modern theme and 10-15 plugins:
| Metric | Shared Hosting (2 GB) | Oracle Cloud Free (24 GB) |
|---|---|---|
| Time to First Byte (TTFB) | 400-800 ms | 50-150 ms |
| Largest Contentful Paint | 2.5-4.0 s | 0.8-1.5 s |
| PHP Response Time | 200-500 ms | 30-80 ms |
| Concurrent Users Before Slowdown | 10-20 | 200+ |
| Google PageSpeed Score (Mobile) | 40-65 | 85-100 |
These are not theoretical numbers. The difference comes from one thing: dedicated resources versus shared resources. When your PHP workers do not compete for memory, when MySQL has enough buffer pool to cache your entire database, when OPcache keeps compiled PHP in memory permanently, everything is faster.
Why This Matters for SEO
Google has used page speed as a ranking factor since 2010, and Core Web Vitals became a ranking signal in 2021. A faster site directly impacts:
- Search rankings: Google prefers faster sites, all else being equal
- Bounce rate: 53% of mobile visitors leave if a page takes longer than 3 seconds to load
- Conversion rate: Every 100ms improvement in load time increases conversions by 1-2%
- Crawl budget: Googlebot can crawl more pages per visit on faster sites
Moving from shared hosting to Oracle Cloud's free tier is not just a cost savings. It is an SEO upgrade.
Step-by-Step: WordPress on Oracle Cloud
Here is the high-level walkthrough of deploying WordPress on Oracle Cloud. Each step includes the key commands and decisions you will need to make.
Note: If you want a guided, interactive setup experience, the WordPress Self-Host Autopilot walks you through every command with explanations and confirmation prompts. What follows here is the manual process for those who prefer to work independently.
Step 1: Create an Oracle Cloud Account
- Go to cloud.oracle.com and click "Sign Up for Free"
- Enter your email and personal information
- Select a Home Region (choose one close to your target audience; US East Ashburn is a solid default for North American traffic)
- Add a credit card for verification (you will not be charged for Always Free resources)
- Wait for account activation (usually instant, sometimes up to 24 hours)
Important: Your home region cannot be changed after account creation. Choose carefully. If your audience is primarily in Europe, pick a European region. If North America, pick Ashburn or Phoenix.
Step 2: Launch an ARM Instance (Always Free)
- In the OCI Console, navigate to Compute > Instances
- Click Create Instance
- Configure the instance:
- Name:
wordpress-server(or whatever you prefer) - Image: Ubuntu 22.04 (or the latest LTS available)
- Shape: Ampere A1 Flex (this is the Always Free ARM shape)
- OCPUs: 4
- Memory: 24 GB
- Boot Volume: 100 GB (up to 200 GB available)
- Name:
- Under Networking, ensure a public IP is assigned
- Under SSH Keys, either upload your existing public key or let Oracle generate a key pair (download and save the private key immediately)
- Click Create
The instance will be provisioned in 1-2 minutes. Note the public IP address displayed in the instance details.
Step 3: SSH Into Your Server
Open your terminal and connect:
ssh -i /path/to/your/private-key ubuntu@YOUR_PUBLIC_IP
If you generated the key through Oracle's console, you may need to set the permissions first:
chmod 600 /path/to/your/private-key
Once connected, update the system:
sudo apt update && sudo apt upgrade -y
Step 4: Open Firewall Ports
Oracle Cloud uses two layers of firewalls: the VCN Security List (cloud-level) and the OS-level firewall. You need to open ports in both.
Cloud-level (OCI Console):
- Navigate to your VCN > Security Lists > Default Security List
- Add Ingress Rules for ports 80 (HTTP) and 443 (HTTPS)
OS-level (on the server):
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
sudo netfilter-persistent save
Step 5: Install NGINX
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
Verify NGINX is running by visiting http://YOUR_PUBLIC_IP in a browser. You should see the default NGINX welcome page.
Step 6: Install PHP 8.x and Required Extensions
WordPress requires PHP with specific extensions. Install PHP 8.2 (or latest stable 8.x) with all necessary modules:
sudo apt install php8.2-fpm php8.2-mysql php8.2-curl php8.2-gd \
php8.2-mbstring php8.2-xml php8.2-zip php8.2-intl php8.2-soap \
php8.2-bcmath php8.2-imagick php8.2-opcache php8.2-redis -y
Verify the installation:
php -v
You should see PHP 8.2.x (or whichever version you installed).
Step 7: Install MariaDB
MariaDB is a drop-in replacement for MySQL with better performance on ARM:
sudo apt install mariadb-server mariadb-client -y
sudo systemctl start mariadb
sudo systemctl enable mariadb
Secure the installation:
sudo mysql_secure_installation
Follow the prompts: set a root password, remove anonymous users, disallow remote root login, remove test database, and reload privileges.
Create a WordPress database and user:
sudo mysql -u root -p
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Replace YOUR_STRONG_PASSWORD_HERE with a strong, unique password.
Step 8: Download and Configure WordPress
cd /tmp
curl -O https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo mv wordpress /var/www/wordpress
sudo chown -R www-data:www-data /var/www/wordpress
sudo chmod -R 755 /var/www/wordpress
Create the WordPress configuration:
cd /var/www/wordpress
sudo cp wp-config-sample.php wp-config.php
Edit wp-config.php with your database credentials:
sudo nano wp-config.php
Update these lines:
define('DB_NAME', 'wordpress');
define('DB_USER', 'wpuser');
define('DB_PASSWORD', 'YOUR_STRONG_PASSWORD_HERE');
define('DB_HOST', 'localhost');
Also update the authentication keys and salts. Visit api.wordpress.org/secret-key/1.1/salt/ and paste the generated keys into your config file, replacing the placeholder lines.
Step 9: Configure NGINX for WordPress
Remove the default NGINX site and create a WordPress configuration:
sudo rm /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/wordpress
Add this configuration (replace yourdomain.com with your actual domain):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/wordpress;
index index.php index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml text/javascript image/svg+xml;
# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# WordPress pretty permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP processing
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
}
# Deny access to sensitive files
location ~ /\.ht {
deny all;
}
location = /wp-config.php {
deny all;
}
# Deny access to xmlrpc.php
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
# Limit upload size
client_max_body_size 64M;
}
Enable the site and test the configuration:
sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 10: Set Up SSL with Let's Encrypt
Before setting up SSL, point your domain to your server's public IP address by creating an A record with your domain registrar.
Install Certbot:
sudo apt install certbot python3-certbot-nginx -y
Obtain and install the certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will automatically modify your NGINX configuration to add HTTPS redirects and SSL settings.
Set up auto-renewal (Certbot does this by default, but verify):
sudo certbot renew --dry-run
Your site now has HTTPS with automatic certificate renewal every 90 days.
Step 11: Complete WordPress Installation
Visit https://yourdomain.com in your browser. You will see the WordPress installation wizard:
- Select your language
- Enter your site title
- Create your admin username and password
- Enter your email address
- Click "Install WordPress"
Your WordPress site is live.
NGINX Configuration for WordPress: Key Directives
The NGINX configuration in Step 9 covers the basics, but there are additional directives worth understanding for production WordPress hosting.
PHP Processing
The fastcgi_pass directive tells NGINX where to send PHP requests. On Ubuntu with PHP-FPM, this is a Unix socket:
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
Unix sockets are faster than TCP connections (127.0.0.1:9000) because they skip the network stack entirely. Always use sockets when PHP-FPM and NGINX are on the same server.
The buffer directives prevent 502 errors on pages with large outputs (WooCommerce checkout pages, large admin panels):
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
Static File Caching
Static assets (images, CSS, JavaScript, fonts) do not change between deployments. Tell browsers to cache them aggressively:
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
The immutable flag tells browsers the file will never change at this URL. When you update CSS or JS, WordPress appends version query strings (?ver=6.4.3), so cached versions are naturally invalidated.
Gzip Compression
Gzip reduces the size of text-based responses by 60-80%:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml text/javascript image/svg+xml;
gzip_comp_level 5 is the sweet spot. Higher levels (6-9) use significantly more CPU for marginal size reduction. Level 5 gives roughly 90% of maximum compression at a fraction of the CPU cost.
Security Headers
These headers protect against common web attacks:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
- X-Frame-Options: Prevents your site from being embedded in iframes (clickjacking protection)
- X-Content-Type-Options: Prevents MIME type sniffing
- X-XSS-Protection: Enables browser-level XSS filtering
- Referrer-Policy: Controls what information is sent in the Referer header
- Content-Security-Policy: Restricts which resources the browser can load
- Permissions-Policy: Disables unused browser APIs
FastCGI Cache (Advanced)
For high-traffic sites, NGINX can cache rendered PHP pages and serve them without touching PHP at all. This is the single most impactful performance optimization for WordPress:
# In the http {} block (nginx.conf):
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m
inactive=60m max_size=512m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# In the server {} block:
set $skip_cache 0;
# Do not cache POST requests
if ($request_method = POST) { set $skip_cache 1; }
# Do not cache URLs with query strings
if ($query_string != "") { set $skip_cache 1; }
# Do not cache WordPress admin or login pages
if ($request_uri ~* "/wp-admin/|/wp-login.php") { set $skip_cache 1; }
# Do not cache for logged-in users
if ($http_cookie ~* "wordpress_logged_in") { set $skip_cache 1; }
location ~ \.php$ {
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache-Status $upstream_cache_status;
# ... rest of PHP config
}
With FastCGI caching enabled, cached page requests are served directly from NGINX memory in under 5ms. Your 24 GB of RAM can cache thousands of pages simultaneously.
SSL Setup with Let's Encrypt
Why HTTPS Is Non-Negotiable
HTTPS is not optional in 2026. Here is why:
SEO: Google confirmed HTTPS as a ranking signal in 2014. Sites without HTTPS are actively penalized in search results.
Browser Warnings: Chrome, Firefox, and Safari all display "Not Secure" warnings on HTTP pages. Visitors see this and leave.
Data Integrity: HTTPS prevents man-in-the-middle attacks that could inject ads or malware into your pages.
API Requirements: Many modern JavaScript APIs (geolocation, service workers, push notifications) require HTTPS.
Trust: Visitors expect the lock icon. Its absence signals unprofessionalism.
Certbot Configuration
Certbot handles certificate issuance, NGINX configuration, and automatic renewal. After running sudo certbot --nginx, it modifies your NGINX config to add:
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
Auto-Renewal
Let's Encrypt certificates expire every 90 days. Certbot installs a systemd timer that automatically renews certificates before expiration:
# Verify the timer is active
sudo systemctl status certbot.timer
You can also add a cron job as a safety net:
sudo crontab -e
Add:
0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
This checks for renewal daily at 3 AM and reloads NGINX if a certificate was renewed.
Security Hardening for WordPress
Self-hosting means you are responsible for security. Here is a comprehensive hardening checklist covering both server-level and WordPress-level protections.
Server-Level Security
1. SSH Key Authentication Only
Disable password-based SSH login to prevent brute force attacks:
sudo nano /etc/ssh/sshd_config
Set these values:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
Restart SSH:
sudo systemctl restart sshd
2. UFW Firewall
Set up a firewall that only allows necessary traffic:
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
This blocks all incoming traffic except SSH (port 22), HTTP (port 80), and HTTPS (port 443).
3. Fail2ban
Fail2ban monitors log files and bans IP addresses that show malicious behavior:
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Add a WordPress-specific jail:
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
findtime = 600
Create the filter:
sudo nano /etc/fail2ban/filter.d/wordpress.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
^<HOST> .* "POST /xmlrpc.php
Restart fail2ban:
sudo systemctl restart fail2ban
4. Automatic Security Updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
This automatically installs security patches for Ubuntu packages, including PHP, NGINX, and MariaDB.
WordPress-Level Security
1. Disable XML-RPC
XML-RPC is a legacy API that most sites do not need. It is commonly exploited for brute force attacks and DDoS amplification. The NGINX configuration above already blocks it, but you can also disable it in WordPress:
Add to wp-config.php:
add_filter('xmlrpc_enabled', '__return_false');
2. Hide WordPress Version
Do not advertise which WordPress version you run:
Add to your theme's functions.php:
remove_action('wp_head', 'wp_generator');
3. Limit Login Attempts
Install the "Limit Login Attempts Reloaded" plugin or add this to wp-config.php:
define('WP_LOGIN_TIMEOUT', 300);
Combined with fail2ban, this creates a two-layer defense against brute force attacks.
4. Change the Default Login URL
The default /wp-login.php and /wp-admin paths are targeted by automated bots. Use a plugin like WPS Hide Login to change the login URL to something unique.
5. Set Correct File Permissions
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;
sudo chmod 400 /var/www/wordpress/wp-config.php
The wp-config.php permission of 400 means only the file owner can read it, and nobody can write to it. This is the most sensitive file on your WordPress installation.
Performance Optimization
With 24 GB of RAM at your disposal, you can implement performance optimizations that shared hosting does not allow.
OPcache Configuration
OPcache stores compiled PHP bytecode in shared memory, eliminating the need to parse PHP files on every request. Edit the OPcache configuration:
sudo nano /etc/php/8.2/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.enable_cli=0
With 24 GB available, allocating 256 MB to OPcache is generous. This caches your entire WordPress codebase plus all plugin code in memory.
Redis Object Cache
WordPress makes hundreds of database queries per page load. Redis caches the results of these queries in memory:
sudo apt install redis-server -y
sudo systemctl enable redis-server
Configure Redis memory:
sudo nano /etc/redis/redis.conf
Set:
maxmemory 512mb
maxmemory-policy allkeys-lru
Install the Redis Object Cache plugin in WordPress and activate it. Add to wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
With Redis, repeated database queries are served from memory in microseconds instead of milliseconds. For sites with complex queries (WooCommerce, membership sites, large blogs), this can reduce page generation time by 50-80%.
PHP-FPM Tuning
Edit the PHP-FPM pool configuration:
sudo nano /etc/php/8.2/fpm/pool.d/www.conf
With 24 GB of RAM, you can run many more PHP workers than the default configuration:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
Each PHP-FPM worker uses approximately 30-60 MB of RAM. With 50 workers at 60 MB each, that is 3 GB maximum. You still have 21 GB for MySQL, Redis, OPcache, and the operating system.
Image Optimization
Large images are the most common cause of slow WordPress pages. Server-level optimizations:
- WebP conversion: Use the WebP Express plugin to automatically convert uploaded images to WebP format (30-50% smaller than JPEG)
- Lazy loading: WordPress 5.5+ includes native lazy loading. Verify it is enabled.
- NGINX WebP serving: Add to your NGINX config to serve WebP images when the browser supports them:
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
Backup Strategy
Self-hosting means backups are your responsibility. Here is a reliable, automated backup strategy that costs nothing.
Database Backups
Create a backup script:
sudo nano /usr/local/bin/wp-backup-db.sh
#!/bin/bash
BACKUP_DIR="/var/backups/wordpress"
DATE=$(date +%Y-%m-%d_%H%M)
mkdir -p $BACKUP_DIR
# Dump database
mysqldump -u wpuser -p'YOUR_PASSWORD' wordpress | gzip > "$BACKUP_DIR/db_$DATE.gz"
# Keep only last 30 days
find $BACKUP_DIR -name "db_*.gz" -mtime +30 -delete
sudo chmod +x /usr/local/bin/wp-backup-db.sh
Schedule daily backups:
sudo crontab -e
Add:
0 2 * * * /usr/local/bin/wp-backup-db.sh
File Backups
WordPress files change less frequently than the database. Weekly file backups are sufficient:
sudo nano /usr/local/bin/wp-backup-files.sh
#!/bin/bash
BACKUP_DIR="/var/backups/wordpress"
DATE=$(date +%Y-%m-%d)
mkdir -p $BACKUP_DIR
# Backup WordPress files (excluding cache and temp files)
tar -czf "$BACKUP_DIR/files_$DATE.tar.gz" \
--exclude='/var/www/wordpress/wp-content/cache' \
--exclude='/var/www/wordpress/wp-content/upgrade' \
/var/www/wordpress/wp-content/
# Keep only last 4 weeks
find $BACKUP_DIR -name "files_*.tar.gz" -mtime +28 -delete
Off-Server Storage
Backups stored on the same server that hosts your site are not real backups. If the server fails, you lose everything. Sync backups to an external location:
Option 1: Oracle Object Storage (Free)
Oracle's free tier includes 10 GB of object storage. Use the OCI CLI to sync backups:
oci os object put --bucket-name wp-backups --file /var/backups/wordpress/db_latest.gz
Option 2: Backblaze B2 (Cheap)
Backblaze B2 costs $0.005/GB/month. For WordPress backups, you are looking at pennies per month:
b2 upload-file wp-backups /var/backups/wordpress/db_latest.gz db_latest.gz
Option 3: rsync to Another Server
If you have access to any other server (even a Raspberry Pi at home):
rsync -avz /var/backups/wordpress/ user@backup-server:/backups/wordpress/
Restore Procedure
If you need to restore from backup:
Database:
gunzip < /var/backups/wordpress/db_2026-02-18_0200.gz | mysql -u wpuser -p wordpress
Files:
cd /var/www/wordpress
tar -xzf /var/backups/wordpress/files_2026-02-18.tar.gz
sudo chown -R www-data:www-data wp-content/
Test your restores periodically. A backup you have never tested is a backup that might not work when you need it.
Common Mistakes to Avoid
After helping people set up self-hosted WordPress through GenAI Unplugged resources, these are the most common mistakes I see.
1. Running Everything as Root
Do not run WordPress as the root user. Create a dedicated web user (Ubuntu uses www-data by default for NGINX), and never SSH in as root. The PermitRootLogin no setting in your SSH config prevents this.
Running as root means a single WordPress vulnerability gives an attacker full server access.
2. No Backups
"I'll set up backups later" is the most expensive sentence in self-hosting. Set up automated backups on day one. The backup section above takes 10 minutes to implement.
3. Skipping SSL
Some people think SSL can wait because they are "just testing." Do not do this. Set up SSL immediately:
- Search engines index your site within days
- If they index the HTTP version first, you are fighting redirect chains later
- Chrome marks HTTP sites as "Not Secure" immediately
4. Using Apache Instead of NGINX
Apache is the traditional WordPress web server, and many tutorials still recommend it. For a self-hosted setup with limited resources (or even abundant resources), NGINX is the better choice:
- NGINX uses significantly less memory per connection
- NGINX handles static files natively without spawning processes
- NGINX's event-driven architecture is better suited for high concurrency
- FastCGI caching in NGINX eliminates the need for many caching plugins
The only reason to use Apache is if you need .htaccess files. NGINX handles everything .htaccess does through its server configuration, more efficiently.
5. Not Monitoring Disk Space
Oracle's free tier gives you 200 GB, which seems like a lot. But log files, backup files, WordPress upload directories, and database growth can consume space over time.
Set up a simple monitor:
# Add to crontab - alerts when disk usage exceeds 80%
0 6 * * * df -h / | awk 'NR==2 && int($5) > 80 {print "WARNING: Disk usage at "$5}' | mail -s "Disk Alert" you@email.com
Or simpler: check df -h manually every month.
6. Ignoring PHP and WordPress Updates
Auto-updates for WordPress core are enabled by default. Do not disable them. For PHP, unattended-upgrades handles security patches.
But major PHP version upgrades (8.2 to 8.3, for example) require manual intervention. Check compatibility with your plugins before upgrading.
7. Installing Too Many Plugins
Every plugin adds PHP code that runs on every page load. On shared hosting, this is devastating. On Oracle Cloud's free tier with 24 GB RAM, the performance impact is smaller, but plugin bloat still increases:
- Attack surface (more code = more potential vulnerabilities)
- Update maintenance burden
- Potential plugin conflicts
- Database bloat from plugins that store excessive data
Audit your plugins quarterly. If you installed something six months ago and do not use it, delete it.
8. Not Setting Up a Swap File
Even with 24 GB of RAM, a swap file provides a safety net for unexpected memory spikes:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
This creates a 2 GB swap file that the OS uses only when physical RAM is exhausted. It prevents out-of-memory crashes during traffic spikes or runaway processes.
When Managed Hosting Still Makes Sense
I am not going to pretend self-hosting is right for everyone. Here are legitimate reasons to pay for managed WordPress hosting:
You Have Zero Technical Interest
If reading this article felt like a foreign language and you have no desire to learn, managed hosting is the right choice. Running a server requires occasional troubleshooting: a failed PHP upgrade, a full disk, an SSL renewal that did not fire. If those sentences cause anxiety rather than curiosity, pay someone else to handle it.
The WordPress Self-Host Autopilot reduces the technical barrier significantly by guiding you through every command with explanations. But you still need basic comfort with a terminal. If the terminal is a hard no, managed hosting serves you better.
You Need Guaranteed Uptime SLAs
Oracle Cloud's free tier does not come with an SLA. Oracle can, in theory, reclaim Always Free instances during capacity shortages (though this is exceptionally rare and has not happened in practice). If your business absolutely requires a contractual uptime guarantee (99.9% or higher), you need a paid hosting plan that provides one.
For most blogs, portfolios, and small business sites, the practical uptime of Oracle Cloud's free tier is excellent. But if downtime directly costs you thousands of dollars per hour, an SLA matters.
You Manage Multiple Client Sites
If you run a web development agency managing 50+ WordPress sites, the operational overhead of self-hosting each one on separate Oracle Cloud instances is substantial. Managed hosting platforms like WP Engine, Kinsta, or Cloudways provide multi-site dashboards, one-click staging, and centralized billing that saves hours of management time.
The math changes when your time managing servers exceeds what you would pay for managed hosting.
You Need Specialized Hosting Features
Some managed hosts provide features that are genuinely difficult to replicate on a self-hosted setup:
- WP Engine's EverCache: Proprietary caching layer with edge nodes
- Kinsta's Google Cloud Premium Tier Network: Optimized global routing
- Cloudways' Managed Database Clustering: Automatic failover
- Flywheel's Collaboration Tools: Designer-focused workflows
If you specifically need these features for your use case, the premium is justified.
The Decision Framework
Ask yourself these three questions:
- Am I comfortable running commands in a terminal? If yes, self-hosting is viable.
- Is my site critical enough to need a contractual uptime guarantee? If yes, consider managed hosting.
- Am I willing to spend 1-2 hours per month on server maintenance? If yes, self-hosting saves you significant money.
If you answered yes to 1 and 3 but no to 2, self-hosting on Oracle Cloud is the clear financial winner.
Getting Started: Your Next Step
You have two paths forward:
Path 1: Do It Yourself
Use this article as your reference. Follow the steps, troubleshoot as needed, and learn the systems by building them. It is the most educational approach, and it costs nothing but your time.
Path 2: Guided Setup
The WordPress Self-Host Autopilot is a Claude Code skill that walks you through every command interactively. It explains what each command does before you run it, confirms before making changes, and handles the NGINX, PHP, MySQL, SSL, and security configuration in a structured sequence. It turns a multi-hour process into a 45-minute guided session.
Either way, you end up with a WordPress site running on 24 GB of dedicated RAM, with HTTPS, security hardening, and performance optimization. For $0/month. While your old hosting company sends invoices to someone else.
The hosting industry charges $300-600/year for 2 GB of shared RAM. Oracle Cloud gives you 24 GB of dedicated RAM for free. The only question is whether you will make the switch.
Ready to stop paying for hosting? The WordPress Self-Host Autopilot guides you through every command.
Get the Autopilot — $49One-time purchase. 14-day money-back guarantee.
FAQ
Can you really host WordPress for free?
Yes. Oracle Cloud's Always Free tier provides ARM instances with up to 24 GB RAM, 4 OCPUs, and 200 GB storage, permanently free. WordPress, NGINX, PHP, and MySQL all run on this instance. Your only cost is a domain name ($10-15/year).
Is Oracle Cloud's free tier really free forever?
Oracle calls it "Always Free" and has maintained it since 2019. Unlike AWS or GCP free tiers that expire after 12 months, Oracle's Always Free resources have no expiration. A credit card is required for sign-up verification, but Always Free resources are never billed.
How does Oracle Cloud compare to shared hosting for WordPress?
Oracle Cloud's free tier gives you 24 GB RAM and 4 dedicated OCPUs. Most shared hosting gives you 1-4 GB shared RAM. Your WordPress site will be significantly faster on Oracle Cloud because resources are not shared with hundreds of other sites.
Do I need to know Linux to self-host WordPress?
Basic terminal comfort is helpful, but not required. The WordPress Self-Host Autopilot guides you through every command with explanations. If you can copy-paste commands into a terminal, you can complete the setup.
Can I migrate my existing WordPress site to Oracle Cloud?
Yes. Use standard WordPress migration methods: the All-in-One WP Migration plugin, manual file and database transfer, or hosting-provided migration tools. The Autopilot sets up the server environment. Standard WordPress migration tools handle the content.
What about WordPress updates and maintenance?
WordPress auto-updates work normally. PHP and system updates can be automated with unattended-upgrades. The main maintenance task is occasional server reboots for kernel updates. Oracle Cloud handles most infrastructure maintenance automatically.
Is self-hosted WordPress secure?
With proper configuration, self-hosted WordPress is as secure or more secure than managed hosting. The setup configures SSH key authentication, UFW firewall, fail2ban, security headers, and automatic SSL renewal. You have full control over your security configuration.