Why Oracle Cloud Free Tier?

Oracle Cloud's free tier is genuinely the best free cloud offering in 2025. Unlike AWS or GCP free tiers that expire after 12 months, OCI's Always Free resources never expire. For a Laravel SaaS with moderate traffic, you can run a full production stack at zero cost — forever.

CloudFree ComputeFree RAMExpires?Storage
Oracle OCI4 OCPU (ARM)24 GB✓ Never200 GB
AWS Free Tier1 vCPU (t2.micro)1 GB✗ 12 months30 GB
GCP Free Tier1 vCPU (e2-micro)1 GB✓ Never30 GB
Azure Free1 vCPU (B1S)1 GB✗ 12 months32 GB
☁️ What We'll Build

Ubuntu 22.04 VM → PHP 8.3 + Nginx → Laravel app → MySQL 8 → Let's Encrypt SSL → OCI firewall rules → auto-deploy with Git. Full production stack, $0/month forever.

Internet
User Request
OCI VCN
Security List :80/:443
Nginx
Reverse Proxy + SSL
Laravel
PHP 8.3 + FPM
MySQL 8
localhost:3306

Step 1 — Create OCI Instance

Sign up at cloud.oracle.com (free, no credit card required for Always Free resources). Then create your compute instance:

01
Compute → Instances → Create Instance
Go to OCI Console → Compute → Instances. Click Create instance.
02
Choose Image: Ubuntu 22.04 Minimal
Under "Image and shape" click Change image. Select Canonical Ubuntu → 22.04 (Minimal). Lighter, faster, less attack surface.
03
Shape: VM.Standard.A1.Flex (ARM) FREE
Click Change shape → Ampere → VM.Standard.A1.Flex. Set 2 OCPU, 12 GB RAM (max Always Free allocation per instance).
04
Generate SSH Key Pair
Click Save private key. Download the .key file. You'll need it for SSH access. Keep it safe — you cannot re-download it.
05
Boot Volume: 50 GB FREE
Under Storage, set boot volume to 50 GB (Always Free includes 200 GB total across instances). Click Create and wait ~2 minutes.

Step 2 — Server Setup

SSH into your instance using the private key. The public IP is shown on the instance details page.

terminal — SSH connect
# Set key permissions (required on Linux/Mac)
chmod 400 ~/Downloads/your-key.key

# SSH into instance (ubuntu is default user for Ubuntu images)
ssh -i ~/Downloads/your-key.key ubuntu@YOUR_PUBLIC_IP

# First — update everything
sudo apt update && sudo apt upgrade -y

Install PHP 8.3 + Extensions

bash — PHP 8.3 install
# Add Ondrej PHP PPA (most up-to-date PHP packages)
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

# Install PHP 8.3 + all Laravel required extensions
sudo apt install -y \
  php8.3 \
  php8.3-fpm \
  php8.3-cli \
  php8.3-mbstring \
  php8.3-xml \
  php8.3-curl \
  php8.3-mysql \
  php8.3-zip \
  php8.3-bcmath \
  php8.3-tokenizer \
  php8.3-gd \
  php8.3-redis \
  unzip git

# Verify
php -v
# PHP 8.3.x (cli) ...

# Install Composer globally
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version

Step 3 — Nginx Setup

bash — Nginx install
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Create the Nginx server block for your Laravel app:

/etc/nginx/sites-available/laravel
server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;

    root /var/www/laravel/public;
    index index.php index.html;

    # Laravel SPA / API — all requests to index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    # Block access to .env and hidden files
    location ~ /\.(?!well-known).* {
        deny all;
    }

    # Static asset caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
        expires    30d;
        add_header Cache-Control "public, no-transform";
    }

    client_max_body_size 50M;
    error_log  /var/log/nginx/laravel-error.log;
    access_log /var/log/nginx/laravel-access.log;
}

# Enable site
# sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
# sudo nginx -t && sudo systemctl reload nginx
bash — enable & test
# Enable site, remove default, reload
sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t            # must show: syntax is ok
sudo systemctl reload nginx

Step 4 — MySQL 8 Setup

bash — MySQL install & secure
sudo apt install mysql-server -y
sudo systemctl enable mysql

# Run security wizard — set root password, remove test DB
sudo mysql_secure_installation

# Create Laravel database and user
sudo mysql -u root -p

-- Inside MySQL shell:
CREATE DATABASE laravel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
🔒 Security Rule

Never open MySQL port 3306 to the internet in OCI Security List. MySQL should only be accessible on localhost. Use SSH tunnels for remote DB management tools like TablePlus or DBeaver.

Step 5 — Deploy Laravel App

bash — clone & configure
# Create web directory and clone your project
sudo mkdir -p /var/www/laravel
sudo chown ubuntu:ubuntu /var/www/laravel
cd /var/www/laravel

git clone https://github.com/youruser/your-laravel-app.git .

# Install dependencies (no dev packages on production)
composer install --optimize-autoloader --no-dev

# Copy and configure environment
cp .env.example .env
nano .env

# ── Inside .env, set these: ─────────────────
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=StrongPassword123!
# ────────────────────────────────────────────

# Generate app key, run migrations, cache config
php artisan key:generate
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Set correct permissions
sudo chown -R www-data:www-data /var/www/laravel
sudo chmod -R 755 /var/www/laravel
sudo chmod -R 775 /var/www/laravel/storage
sudo chmod -R 775 /var/www/laravel/bootstrap/cache

Step 6 — SSL with Let's Encrypt

Free SSL certificate via Certbot. Auto-renews every 90 days — set it and forget it.

bash — Certbot SSL setup
# Install Certbot + Nginx plugin
sudo apt install certbot python3-certbot-nginx -y

# Request certificate (auto-configures Nginx for HTTPS)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Certbot will ask for email, agree to TOS
# It auto-modifies your Nginx config to add:
#   listen 443 ssl;
#   ssl_certificate / ssl_certificate_key paths
#   HTTPS redirect from port 80

# Test auto-renewal
sudo certbot renew --dry-run

# Auto-renew cron (already added by Certbot, verify):
sudo systemctl status certbot.timer
✅ Verify HTTPS

Visit https://yourdomain.com — you should see the green padlock. Test SSL quality at ssllabs.com/ssltest — aim for A or A+ rating.

Step 7 — OCI Firewall & Security Rules

OCI has two layers of firewall: the OCI Security List (cloud level) and Ubuntu UFW (OS level). Both must allow traffic.

OCI Security List (VCN)

Go to OCI Console → Networking → Virtual Cloud Networks → your VCN → Security Lists → Default Security List. Add these Ingress Rules:

PortProtocolSourcePurpose
22TCPYour IP onlySSH Access
80TCP0.0.0.0/0HTTP (Certbot)
443TCP0.0.0.0/0HTTPS
3306TCP❌ Never openMySQL — localhost only

Ubuntu UFW Firewall

bash — UFW setup
# Configure UFW — whitelist only what's needed
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp       # SSH
sudo ufw allow 80/tcp       # HTTP
sudo ufw allow 443/tcp      # HTTPS

# Enable — type 'y' when prompted
sudo ufw enable
sudo ufw status verbose

Step 8 — Auto Deploy with Git Hook

Manual deploy every time is tedious. Set up a Git post-receive hook so git push automatically deploys to production:

bash — deploy script
# Create deploy script
nano /var/www/laravel/deploy.sh
deploy.sh — zero-downtime deploy
#!/bin/bash
set -e

APP_DIR="/var/www/laravel"
echo "🚀 Starting deployment..."

cd $APP_DIR

# Pull latest code
git pull origin main

# Install/update dependencies
composer install --optimize-autoloader --no-dev --quiet

# Run migrations (safe with --force in CI)
php artisan migrate --force

# Clear & rebuild all caches
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Reset permissions
sudo chown -R www-data:www-data $APP_DIR/storage
sudo chown -R www-data:www-data $APP_DIR/bootstrap/cache

# Restart PHP-FPM gracefully
sudo systemctl reload php8.3-fpm

echo "✅ Deployment complete!"
bash — make executable & test
chmod +x /var/www/laravel/deploy.sh

# Test deploy manually first
bash /var/www/laravel/deploy.sh

# Add sudo rule so deploy.sh can reload php-fpm without password
echo "ubuntu ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload php8.3-fpm" | sudo tee /etc/sudoers.d/laravel-deploy
🏁 You're Live — Final Checklist

What you've built: A production Laravel stack on OCI Always Free — Ubuntu 22.04 + PHP 8.3 + Nginx + MySQL 8 + Let's Encrypt SSL + UFW firewall + automated deploy script. Total monthly cost: $0.00.

Next steps to harden: Set up MySQL automated backups to OCI Object Storage, configure Laravel queue workers with Supervisor, add Redis for caching and session management, and set up Fail2ban to block SSH brute force attempts.

OCI Certified tip: The free ARM instances (VM.Standard.A1.Flex) are ideal for Laravel. PHP 8.3 + PHP-FPM performs extremely well on ARM — I've seen 3–4x better requests/second vs t2.micro on AWS at similar CPU.

👨‍💻
Sonu Sahani
OCI Certified Architect Associate · CodeCraft Systems

6+ years building & deploying production Laravel apps on OCI, VPS, and cPanel environments for UK, USA & global clients. Oracle Cloud Infrastructure 2025 Certified Architect Associate — this guide is from real production deployments.