Oracle Cloud Free Tier: How I Host Production Web Apps for $0/month (Forever)

Eugene Sviryd ·
Oracle Cloud Free Tier: How I Host Production Web Apps for $0/month (Forever)

Why Oracle Cloud Free Tier Exists (And Why It Is Legitimate)

Let me address the elephant in the room: yes, it is actually free. Forever. Not "free for 12 months" like AWS. Not "free with $200 credits that expire" like Azure. Oracle's Always Free tier is a permanent allocation that does not expire, does not auto-upgrade, and does not surprise you with a bill.

Why does Oracle do this? Because they are late to the cloud game and need developers to try their platform. Their bet is that some percentage will scale up to paid tiers. But even if you never spend a cent, the Always Free resources remain yours.

Here is what you get:

  • 2 AMD Micro VMs - 1 OCPU, 1 GB RAM each (or 1 ARM VM with up to 4 OCPUs and 24 GB RAM on Ampere A1)
  • 200 GB total block storage across all VMs
  • 10 TB/month outbound data transfer
  • Public IPv4 address included
  • 2 autonomous databases (20 GB each)
  • 10 GB object storage

For a personal portfolio, a side project, a staging environment, or even a low-traffic production app - this is more than enough.

Step 1: Create Your Account and VM

Sign up at cloud.oracle.com. You will need a credit card for identity verification, but you will not be charged, and you can stay on the Always Free tier indefinitely.

Create a compute instance:

  1. Go to Compute → Instances → Create Instance
  2. Choose Ubuntu 22.04 (or 24.04) as the image
  3. Select the VM.Standard.E2.1.Micro shape (Always Free eligible) or the VM.Standard.A1.Flex (Ampere ARM, up to 4 OCPUs free)
  4. Under networking, ensure Assign a public IPv4 address is checked
  5. Upload your own SSH public key (more on this below)
  6. Click Create

The VM will be ready in about 60 seconds. Note the public IP - that is your server's permanent address.

Step 2: SSH Keys - Upload Your Own (This Is Critical)

Oracle gives you the option to generate an SSH key pair during VM creation and download the private key. Do not use this option.

Why? Three reasons:

  1. Browser-generated keys have a habit of breaking. Multiple users have reported that Oracle-generated SSH keys stop working after a while - sometimes after a VM reboot, sometimes randomly. When your key stops working and the console serial connection is your only fallback, you will regret not using your own key.
  2. You should own your keys. Your SSH private key should live in ~/.ssh/ on your machine, generated by you, backed up by you. A key that was generated in a browser, downloaded once, and saved to your Downloads folder is a security incident waiting to happen.
  3. CI/CD integration. If you want GitHub Actions to deploy to your server (and you do), you need the private key stored as a GitHub secret. Starting with your own key makes this straightforward.

Generate a key pair on your local machine if you do not already have one:

bash
# Generate an ED25519 key (modern, fast, secure)
ssh-keygen -t ed25519 -C "your-email@example.com"

# Or RSA 4096 if you need wider compatibility
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"

# Copy the public key (paste this into Oracle's form)
cat ~/.ssh/id_ed25519.pub

Paste the contents of your .pub file into the SSH key field during VM creation. That is it. Your key, your control, no surprises.

Step 3: Open Ports (The Part Oracle Makes Confusing)

This is where most people get stuck. You will SSH into your VM, install nginx, and discover that port 80 is unreachable from the outside world. The reason: Oracle has two firewalls, and you need to open both.

Firewall 1: Oracle Cloud Security Lists (VCN level)

This is the cloud-level firewall. Navigate to:

  1. Networking → Virtual Cloud Networks → your VCN
  2. Security Lists → Default Security List
  3. Add Ingress Rules:
other
# Ingress rule for HTTP
Source CIDR:   0.0.0.0/0
IP Protocol:   TCP
Destination Port Range: 80

# Ingress rule for HTTPS
Source CIDR:   0.0.0.0/0
IP Protocol:   TCP
Destination Port Range: 443

Firewall 2: iptables inside the VM

Oracle's Ubuntu images ship with iptables rules that block all incoming traffic except SSH. Even after opening the cloud security list, your server will reject HTTP connections until you open the OS-level firewall:

bash
# Open port 80 (HTTP) and 443 (HTTPS)
sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT

# Save rules so they persist across reboots
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save

Both firewalls must be open. This trips up everyone the first time. If your site is not loading, check the cloud security list first, then iptables.

Step 4: The Full Server Setup (One Script)

Here is the bootstrap script I use. It installs Python, nginx, configures Gunicorn as a systemd service, and sets up nginx as a reverse proxy - all in one run:

bash
#!/usr/bin/env bash
set -euo pipefail

# Install Python 3.12
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update -qq
sudo apt-get install -y python3.12 python3.12-venv \
    python3.12-dev build-essential nginx

# Open firewall
sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save

# Clone your project
APP_DIR="$HOME/myapp"
git clone git@github.com:you/your-repo.git "$APP_DIR"

# Create virtualenv and install dependencies
python3.12 -m venv "$APP_DIR/.venv"
"$APP_DIR/.venv/bin/pip" install -r "$APP_DIR/requirements.txt"

# Create a systemd service for Gunicorn
sudo tee /etc/systemd/system/myapp.service > /dev/null <<EOF
[Unit]
Description=My App (Gunicorn)
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=$APP_DIR
EnvironmentFile=$APP_DIR/.env
ExecStart=$APP_DIR/.venv/bin/gunicorn myapp.wsgi:application \\
    --workers 2 --bind unix:/run/myapp.sock
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

# Configure nginx reverse proxy
sudo tee /etc/nginx/sites-available/myapp > /dev/null <<EOF
server {
    listen 80;
    server_name _;
    client_max_body_size 20M;

    location /static/ {
        alias $APP_DIR/staticfiles/;
        expires 30d;
    }

    location / {
        proxy_pass http://unix:/run/myapp.sock;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

# Enable and start
sudo systemctl enable myapp && sudo systemctl start myapp
echo "Done. Visit http://$(curl -s ifconfig.me)"

Step 5: SSL with Let's Encrypt (Free HTTPS)

Once your domain's DNS points to the Oracle VM's public IP, adding HTTPS takes two commands:

bash
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com

# Auto-renewal is set up automatically via systemd timer
# Verify with:
sudo certbot renew --dry-run

Certbot modifies your nginx config to listen on 443 with the certificate and redirects port 80 to HTTPS. Free SSL, auto-renewed every 90 days, zero maintenance.

Step 6: CI/CD with GitHub Actions

The final piece: automated deployments on every push to main. Add your SSH private key as a GitHub secret (DEPLOY_SSH_KEY), then create this workflow:

yaml
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: your-server-ip
          username: ubuntu
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd ~/myapp
            git pull origin main
            bash scripts/deploy.sh

Push to main, GitHub SSHs into your Oracle VM, pulls the code, runs migrations, collects static files, and restarts Gunicorn. Full CI/CD for $0.

Oracle Free Tier vs AWS EC2 t2.micro: The Real Comparison

AWS offers a t2.micro in their free tier - but only for 12 months. After that, you pay. Let me compare the two honestly:

other
                        Oracle Free Tier    AWS t2.micro
                        ────────────────    ────────────
Price after year 1      $0 (forever)        ~$8.50/month
vCPUs                   1 OCPU              1 vCPU
RAM                     1 GB (or 24 GB ARM) 1 GB
Storage                 50 GB boot + 150 GB 8 GB (30 GB free)
Outbound transfer       10 TB/month         100 GB/month (then $0.09/GB)
Public IPv4             Included            $3.75/month (since Feb 2024)
Always Free             Yes                 No (12 months only)
ARM option (24GB RAM)   Yes (Ampere A1)     No (Graviton is paid)

The numbers speak for themselves. Oracle gives you more storage, 100x the bandwidth, a free public IP, and it never expires. AWS's free tier was already stingy, and since they started charging $3.75/month for public IPv4 addresses in February 2024, running even a stopped EC2 instance costs money.

The Ampere A1 ARM shape is the real killer. You can allocate up to 4 OCPUs and 24 GB of RAM on the Always Free tier. That is not a hobby box - that is a legitimate server that can run a database, a Django app, and a Redis cache simultaneously.

The Gotchas Nobody Tells You

Oracle Free Tier is excellent, but it has sharp edges:

  • Idle instances get reclaimed. Oracle reserves the right to stop Always Free instances that are "idle" (consistently below 10% CPU and 10% memory usage). Running a lightweight web server usually keeps you above the threshold, but be aware.
  • Availability is limited. Free Tier VMs are popular. If your region is full, you cannot create a new instance. Try different availability domains or regions. Some people script the creation API to retry until a slot opens.
  • The Oracle-generated SSH keys are unreliable. I said it before, I will say it again: upload your own key.
  • The double firewall. Cloud security list + OS iptables. Open both or spend an hour debugging why nginx is not reachable.
  • No email sending. Oracle blocks outbound SMTP (port 25) on Free Tier. Use a transactional email service like Mailgun, SendGrid, or Resend instead.

What I Run on My Free Oracle VM

This very website - the portfolio and blog you are reading right now - runs on an Oracle Cloud Free Tier VM. The stack:

  • Django 5.x + Wagtail 6.x - the CMS and application layer
  • Gunicorn with 2 workers, managed by systemd
  • Nginx as a reverse proxy with static file serving
  • SQLite for the database (perfectly fine for a personal site)
  • Let's Encrypt for free SSL
  • GitHub Actions for zero-touch deployments

Total monthly cost: $0. Total annual cost: $0. The only paid component is the domain name.

The site loads fast, handles all the traffic I need, and deploys automatically when I push to main. For a developer portfolio, a blog, a side project, or a staging environment, Oracle Cloud Free Tier is the best deal in cloud computing. No asterisks.

The cheapest server is the one that costs nothing. The best server is the one you fully control. Oracle Free Tier is both.