πŸ’‘ Assuming you work on macOS

πŸ’‘ Multi-level subdomain (e.g. level2.level1.example.com) SSL may not working

Prerequisites Link to heading

  • Oracle Cloud Account
  • Cloudflare Account
  • Custom Domain

Tech Stack Link to heading

  • Oracle Cloud Infrastructure (OCI)
    • Instance
    • Network
  • Cloudflare
    • DNS
    • SSL/TLS
  • SSH
  • Docker
  • Docker Compose
  • Nginx
  • UFW

Step by step Link to heading

  • Create a Compartment
  • Create a Virtual Cloud Network
    • Creates a VCN
    • Adds an Internet Gateway which enables internet connections
    • Creates and configures public and private subnets for the VCN
    • Sets up route tables and security lists for the subnets
  • Create a Virtual Machine Instance
    • Choose the Name and Compartment
    • Review the Placement
    • Review the Security
    • Review the Image and shape
      • e.g. Ubuntu & VM.Standard.E2.1.Micro
    • Review the Networking
    • Review the Add SSH keys
      • Generate SSH key pair and save to ~/.ssh/
      • Upload the public key to OCI
  • Connect to Your Instance
    • chmod 400 <private_key_file>
      • e.g. chmod 400 my_private_key
    • ssh -i <private_key_file> @
  • Add a Block Volume (Optional)
  • Initial setup
    sudo apt update && sudo apt upgrade -y
    sudo timedatectl set-timezone UTC
    
  • Install Docker suite
    # Install Docker
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
    https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io
    # Install Docker Compose v2 (optional)
    sudo apt install -y docker-compose-plugin
    # Add current USER to docker group
    sudo usermod -aG docker $USER
    # Enable docker to start on boot
    sudo systemctl enable --now docker
    # Check version
    docker version
    docker compose version
    
  • Create app directory
    sudo mkdir welcome
    cd welcome
    
  • Create Docker Compose manifest
    version: "3.9"
    
    services:
      welcome:
      image: docker/welcome-to-docker:latest
      container_name: welcome
      restart: always
      ports:
        - "127.0.0.1:8080:80"   # bind to loopback only (nginx on host proxies to this)
    
  • Bring Compose up
    docker compose up -d
    # Check status
    docker compose ps
    
  • Obtain Cloudflare Origin Certificate
  • Save the files on the host
    sudo mkdir -p /etc/ssl/cloudflare
    sudo tee /etc/ssl/cloudflare/origin.pem > /dev/null <<'EOF'
    ---PASTE THE ORIGIN CERTIFICATE (PEM) HERE---
    EOF
    
    sudo tee /etc/ssl/cloudflare/origin.key > /dev/null <<'EOF'
    ---PASTE THE ORIGIN PRIVATE KEY (PEM) HERE---
    EOF
    
    sudo chmod 600 /etc/ssl/cloudflare/origin.key
    sudo chmod 644 /etc/ssl/cloudflare/origin.pem
    
  • Install nginx
    sudo apt install -y nginx
    
  • Create nginx config
    upstream welcome_upstream {
      server 127.0.0.1:8080;
    }
    
    server {
        listen 80;
        server_name your.domain;
    
        # Redirect all HTTP to HTTPS
        return 301 https://$host$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name your.domain;
    
        ssl_certificate /etc/ssl/cloudflare/origin.pem;
        ssl_certificate_key /etc/ssl/cloudflare/origin.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers off;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;
    
        # HSTS (careful with this in early testing; set long max-age in production)
        add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    
        # Security headers
        add_header X-Frame-Options SAMEORIGIN always;
        add_header X-Content-Type-Options nosniff always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Content-Security-Policy "default-src 'self';" always;
    
        # Recommended limits
        client_max_body_size 10M;
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffering off;
    
        location / {
            proxy_pass http://welcome_upstream;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            # preserve client IPs for logging
        }
    
        # Optional: a simple healthcheck endpoint proxied
        location = /health {
            proxy_pass http://welcome_upstream/;
        }
    
        access_log /var/log/nginx/welcome.access.log;
        error_log /var/log/nginx/welcome.error.log;
    }
    
  • Enable nginx
    sudo ln -s /etc/nginx/sites-available/welcome /etc/nginx/sites-enabled/welcome
    sudo nginx -t
    sudo systemctl restart nginx
    sudo systemctl enable nginx
    
  • Firewall (UFW)
    sudo apt install -y ufw
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    
    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    
    sudo ufw enable
    sudo ufw status verbose
    
  • OCI Network Security
    • The VCN/subnet security lists or Network Security Group allow ingress 80/443/22
  • Cloudflare DNS + SSL settings
    • In Cloudflare DNS
      • create A record: app-name (e.g. welcome) β†’ OCI_PUBLIC_IP (e.g. 152.12.30.45) β†’ Proxied (orange cloud)
    • In Cloudflare SSL/TLS
      • Choose Full (strict)
  • Verify

Reference Link to heading