Console9

How to set up a production web server with Nginx, SSL, and CORS

Install Nginx on Ubuntu, configure server blocks for multiple domains, enable HTTPS with Let's Encrypt, and set up CORS headers for API access.

This guide walks through setting up a production-ready Nginx web server on Ubuntu. By the end, you will have Nginx serving one or more domains over HTTPS with a Let's Encrypt SSL certificate, Cross-Origin Resource Sharing (CORS) headers configured for API endpoints, and basic security hardening applied.

What You Need Before Starting

  • An Ubuntu 22.04 or 24.04 server with root or sudo access
  • A registered domain name with DNS A records pointing to your server's public IP address
  • SSH access to the server
  • Ports 80 (HTTP) and 443 (HTTPS) open in your firewall

Estimated time: 30–45 minutes.

Architecture Overview

This guide uses three components that work together to serve a production website.

Nginxhandles incoming HTTP and HTTPS requests, serves static files, and forwards dynamic requests to backend applications through reverse proxying. Nginx processes client connections using an event-driven architecture that handles thousands of concurrent connections efficiently.

Certbotautomates SSL certificate issuance and renewal through the Let's Encrypt certificate authority. Certbot obtains Domain Validation (DV) certificates using the ACME protocol and configures Nginx to serve HTTPS traffic.

CORS headerscontrol which external domains can access your API endpoints from a browser. The Nginx web server adds Cross-Origin Resource Sharing headers to HTTP responses, which the browser evaluates before allowing cross-origin requests.

Step 1: Install Nginx on Ubuntu

Nginx is available in the default Ubuntu package repositories. Install the Nginx web server using apt:

sudo apt update
sudo apt install nginx

Verify that Nginx is running after installation:

sudo systemctl status nginx

The Nginx service starts automatically after installation. Open a browser and navigate to your server's IP address — you should see the default Nginx welcome page.

Allow HTTP and HTTPS Traffic Through the Firewall

If the Ubuntu firewall (UFW) is enabled, allow Nginx traffic on ports 80 and 443:

sudo ufw allow 'Nginx Full'

For a deeper explanation of Nginx configuration structure, see the Nginx configuration article.

Step 2: Configure Nginx Server Blocks for Your Domain

Nginx uses server blocks (similar to Apache virtual hosts) to serve multiple domains from a single server. Create a server block configuration file for your domain:

sudo nano /etc/nginx/sites-available/{YOUR_DOMAIN}

Add the following Nginx server block configuration:

server {
    listen 80;
    listen [::]:80;
    server_name {YOUR_DOMAIN} www.{YOUR_DOMAIN};
    root /var/www/{YOUR_DOMAIN}/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

This Nginx server block listens on port 80, responds to requests for your domain, and serves files from the /var/www/{YOUR_DOMAIN}/html directory.

Create the document root directory and a test page:

sudo mkdir -p /var/www/{YOUR_DOMAIN}/html
echo "<h1>Server is working</h1>" | sudo tee /var/www/{YOUR_DOMAIN}/html/index.html

Enable the server block by creating a symbolic link in the sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/{YOUR_DOMAIN} /etc/nginx/sites-enabled/

Test the Nginx configuration for syntax errors and reload:

sudo nginx -t
sudo systemctl reload nginx

For hosting multiple domains on the same server, see How to configure multiple domains on one Nginx server.

Step 3: Obtain an SSL Certificate with Certbot for Nginx

Certbot automates SSL certificate issuance from Let's Encrypt. Install Certbot and the Nginx plugin:

sudo apt install certbot python3-certbot-nginx

Run Certbot to obtain a TLS certificate and configure Nginx for HTTPS automatically:

sudo certbot --nginx -d {YOUR_DOMAIN} -d www.{YOUR_DOMAIN}

Certbot performs these actions automatically:

  • Verifies domain ownership using the ACME protocol over HTTP
  • Obtains a Domain Validation (DV) certificate from Let's Encrypt
  • Modifies the Nginx server block to listen on port 443 with SSL
  • Sets up HTTP-to-HTTPS redirection on port 80

Verify Automatic Certificate Renewal

Let's Encrypt certificates expire after 90 days. Certbot installs a systemd timer that renews certificates automatically. Test the renewal process:

sudo certbot renew --dry-run

A successful dry run confirms that automatic renewal will work when the certificate approaches expiration.

Step 4: Configure CORS Headers in Nginx

Cross-Origin Resource Sharing (CORS) headers allow browsers to make requests to your server from different domains. CORS configuration is required when a frontend application on one domain accesses an API on your server's domain.

Add CORS headers to the Nginx server block for the relevant location:

location /api/ {
    # CORS headers for cross-origin API access
    add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
    add_header 'Access-Control-Max-Age' 86400 always;

    # Handle CORS preflight requests
    if ($request_method = 'OPTIONS') {
        return 204;
    }

    proxy_pass http://127.0.0.1:3000;
}

This Nginx CORS configuration allows the specified frontend origin to send cross-origin requests to the /api/ location. The Access-Control-Max-Age header caches preflight responses for 24 hours, which reduces the number of OPTIONS requests.

Reload Nginx to apply the CORS configuration:

sudo nginx -t
sudo systemctl reload nginx

For platform-specific CORS configuration, see the CORS article.

Step 5: Apply Basic Nginx Security Hardening

Nginx exposes its version number in HTTP response headers by default. Hide the Nginx server version to reduce information leakage:

server_tokens off;

Add security headers to protect against common web attacks. Place these directives in the server block:

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;

Test and reload Nginx after adding security headers:

sudo nginx -t
sudo systemctl reload nginx

For a full security hardening walkthrough, see Nginx tutorial: How to secure your Nginx web server.

How to Verify the Complete Setup

Test each component of the web server setup:

  1. HTTPS: Open https://{YOUR_DOMAIN} in a browser. The connection should show a valid SSL certificate from Let's Encrypt.

  2. HTTP redirect: Open http://{YOUR_DOMAIN} in a browser. Nginx should redirect automatically to the HTTPS version.

  3. CORS headers: Send a preflight request using curl and verify the CORS headers appear:

curl -I -X OPTIONS https://{YOUR_DOMAIN}/api/ \
  -H "Origin: https://frontend.example.com" \
  -H "Access-Control-Request-Method: GET"

The response should include the Access-Control-Allow-Origin header.

  1. Security headers: Verify security headers are present:
curl -I https://{YOUR_DOMAIN}

The response should include X-Frame-Options, X-Content-Type-Options, and the Server header should not reveal the Nginx version.

What to Do Next