Thursday, 12 December 2024

Set up a single EC2 instance running Nginx as a load balancer and SSL terminator using Let’s Encrypt

How to set up a single EC2 instance running Nginx as a load balancer and SSL terminator using Let’s Encrypt, directing traffic to three EC2 instances running a Laravel application. This guide assumes a standard AWS VPC environment and that you have control over DNS records for your domain.

Overview of the Architecture

  • Public-facing EC2 (Load Balancer):
    • Runs Nginx.
    • Terminates SSL using Let’s Encrypt certificates.
    • Proxies requests to the 3 backend Laravel application servers.
  • Three Backend EC2 Instances (Laravel App Servers):
    • Run Laravel (with PHP-FPM, Nginx, or Apache).
    • Accessible only within the same VPC, ideally in private subnets.
    • The load balancer communicates with them via private IP addresses.

Prerequisites

  1. AWS Setup:

    • A VPC with subnets. Assume:
      • One public subnet for the load balancer EC2.
      • Private subnets for the Laravel app servers.
    • Security groups properly set up so the load balancer can access the app servers on port 80 (or your chosen port).
  2. EC2 Instances:

    • Load Balancer EC2:
      • Public subnet.
      • Has a public IP or an Elastic IP (EIP) so it’s reachable from the internet.
    • Laravel App EC2s:
      • Typically in private subnets (recommended) or can be public if properly secured.
      • Each running a web server (Nginx/Apache) and PHP-FPM, serving the Laravel app on port 80 internally.
    • Ensure that DNS (e.g., example.com) points to the public IP of the load balancer EC2.
  3. DNS Configuration:

    • In your domain registrar or DNS provider, create an A record for example.com pointing to the load balancer EC2’s public IP.
    • If using a subdomain (e.g., app.example.com), point that subdomain to the load balancer.

Step-by-Step Setup

A. Set Up the Backend Laravel Instances

  1. Install and Run Laravel:

    • On each Laravel EC2 instance, ensure:
      • PHP, Composer, and a web server (Nginx or Apache) are installed.
      • The Laravel code is deployed in /var/www/html or a similar directory.
      • Nginx or Apache is configured to serve the application at http://localhost:80.
    • Test locally on each instance:
      bash
      curl http://localhost
      You should see the Laravel default page or your application’s output.
  2. Security Groups (Backend):

    • The backend servers’ security group should allow inbound traffic on port 80 only from the load balancer’s private IP or security group, not from the public internet.
    • Outbound traffic is usually unrestricted by default.

B. Set Up the Load Balancer EC2 (Nginx + Let’s Encrypt)

  1. Install Nginx: On a Ubuntu-based system:

    bash
    sudo apt-get update sudo apt-get install nginx -y

    Confirm Nginx is running:

    bash
    systemctl status nginx
  2. Open Ports in Security Group (Load Balancer):

    • Inbound: allow HTTP (80) and HTTPS (443) from 0.0.0.0/0 so the public can access the site.
    • Outbound: allow traffic to the backend servers on port 80.
  3. Configure Nginx Upstream for Load Balancing: Create an upstream configuration file:

    bash
    sudo nano /etc/nginx/conf.d/upstream.conf

    Add:

    nginx
    upstream laravel_app { server 10.0.1.10:80; # Private IP of Laravel app server 1 server 10.0.1.11:80; # Private IP of Laravel app server 2 server 10.0.1.12:80; # Private IP of Laravel app server 3 }

    Replace IPs with the actual private IPs of your Laravel servers.

  4. Create Nginx Server Block for HTTP:

    bash
    sudo nano /etc/nginx/sites-available/example.com

    Add:

    nginx
    server { listen 80; server_name example.com www.example.com; # Location for Let's Encrypt HTTP-01 challenge location /.well-known/acme-challenge/ { root /var/www/html; } # Proxy pass to backend location / { proxy_pass http://laravel_app; 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; } }

    Create the root directory for Let’s Encrypt challenges (if it doesn't exist):

    bash
    sudo mkdir -p /var/www/html/.well-known/acme-challenge sudo chown -R www-data:www-data /var/www/html

    Enable the site:

    bash
    sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

    Now http://example.com should route to one of the backend Laravel servers. Test by visiting http://example.com.

C. Obtain and Install Let’s Encrypt SSL Certificate

  1. Install Certbot: On Ubuntu:

    bash
    sudo apt-get install certbot python3-certbot-nginx -y
  2. Run Certbot:

    bash
    sudo certbot --nginx -d example.com -d www.example.com

    Certbot will:

    • Place a challenge file in /.well-known/acme-challenge/ for domain verification.
    • Request an SSL certificate from Let’s Encrypt.
    • Update the Nginx configuration automatically to use HTTPS.
  3. Check the Nginx Configuration After Certbot: After Certbot finishes, it should create or update the example.com config to:

    • Redirect http:// to https://
    • Use ssl_certificate and ssl_certificate_key directives pointing to your Let’s Encrypt certificates.

    A typical final config might look like:

    nginx
    server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location / { proxy_pass http://laravel_app; 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; } }

    Test:

    bash
    sudo nginx -t sudo systemctl reload nginx

    Now, https://example.com should serve the Laravel app over HTTPS.

D. Verify Load Balancing and SSL

  • Open https://example.com in a browser.
  • Confirm the SSL certificate is valid and secured (lock icon in the browser).
  • Refresh multiple times to ensure requests are distributed across the three backend servers (you can check logs on each server to confirm hits).

E. Certificate Renewal

  • Certbot sets up a systemd timer or cron job to auto-renew certificates.
  • Check:
    bash
    systemctl list-timers | grep certbot
    This ensures your SSL certificate is renewed automatically.

F. Additional Considerations

  1. Sticky Sessions / Session Storage: If your Laravel application uses session data (e.g., user logins), consider using a centralized session store such as Redis or a database. Without this, different requests may land on different app servers and cause issues if each server stores sessions locally. Configure SESSION_DRIVER in .env accordingly.

  2. Health Checks & Failover: Consider enabling health checks to ensure Nginx only routes requests to healthy backend servers. You can configure proxy_next_upstream or a specific health check endpoint.

  3. Logging and Monitoring:

    • Nginx logs:
      • Access logs: /var/log/nginx/access.log
      • Error logs: /var/log/nginx/error.log
    • Laravel logs on backend servers:
      Check storage/logs/laravel.log to ensure the requests are being served correctly.
  4. Scaling: If you need more backend servers in the future, just add their IPs to the upstream block and reload Nginx. This makes the system scalable.

Summary:

  • You set up an EC2 instance as a load balancer with Nginx and obtained an SSL certificate from Let’s Encrypt.
  • You pointed your domain name to the load balancer, which then routes traffic to three backend Laravel servers.
  • The setup terminates SSL at the load balancer and distributes requests over HTTP to the Laravel servers internally.
  • With proper DNS, security groups, and upstream configuration, you have a secure, load-balanced environment for your Laravel application.

Thank you

No comments:

Post a Comment

Golang Advanced Interview Q&A