Back to blog

Web Server

Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy

Complete Nginx setup guide - server blocks, Let's Encrypt TLS, HTTP/2, Gzip compression, and proxying to upstream application servers.

  • Nginx
  • Ubuntu
  • Web Server
  • TLS
  • Reverse Proxy
  • Let's Encrypt

SEO Metadata

SEO Title Options

  1. Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL
  2. Configuring Nginx on Ubuntu 20.04: Practical 2026 Guide
  3. Web Server Playbook: Configuring Nginx on Ubuntu 20.04

Meta Description Options

  1. Learn Configuring Nginx on Ubuntu 20.04 with a practical Web Server framework, expert mistakes, implementation steps, examples, FAQ, and schema-ready.
  2. Complete Nginx setup guide - server blocks, Let's Encrypt TLS, HTTP/2, Gzip compression, and proxying to upstream application servers.

URL Slug

configuring-nginx-ubuntu-20-04-virtual-hosts-ssl-reverse-proxy

Focus Keyword

Configuring Nginx on Ubuntu 20.04

Additional LSI Keywords

  • Web Server
  • Nginx
  • Ubuntu
  • TLS
  • Reverse Proxy
  • Let's Encrypt
  • Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy
  • production checklist
  • implementation guide
  • best practices
  • architecture decisions
  • testing strategy

Table of Contents

Article overview

Configuring Nginx on Ubuntu 20.04 is the kind of topic that looks simple until it reaches production. Teams usually discover the real cost late: unclear boundaries, weak defaults, hidden maintenance work, and decisions that seemed harmless when the codebase was small.

The problem gets worse when the article, tutorial, or implementation guide only explains the happy path. This guide closes that gap with a practical framework, a comparison table, common mistakes, and a deep technical section you can use while planning real work.

Keep reading for the non-obvious part: the safest implementation is rarely the most impressive-looking one. It is the one your team can debug, test, document, and evolve without turning every future change into archaeology.

Key Takeaways

  • Configuring Nginx on Ubuntu 20.04 should be evaluated as a production decision, not only as a syntax or tooling choice.
  • The best implementation keeps responsibilities visible, with clear ownership, tests, documentation, and rollback paths.
  • Search visibility improves when practical depth, structured answers, and expert examples live on the same page.

[IMAGE: A mobile-first technical article layout showing the main concept, decision table, implementation checklist, and FAQ blocks. Alt: Configuring Nginx on Ubuntu 20.04 expert guide for Web Server]

What Configuring Nginx on Ubuntu 20.04 means

Configuring Nginx on Ubuntu 20.04 means applying web server knowledge to a concrete engineering decision, then turning that decision into reliable code, documentation, and operational behavior. In practice, it combines the topic's core concepts with trade-off analysis, implementation boundaries, testing strategy, and maintenance discipline.

This is the definition worth optimizing for featured snippets because it avoids hype. It tells the reader what the topic does and what a professional implementation must include.

Why it matters now

The technical web is more crowded than it was a few years ago. Thin tutorials can still get indexed, but they rarely earn trust from senior developers, buyers, AI answer systems, or teams that need production guidance.

For web server topics, the strongest content now has three layers:

  • a clear answer for fast scanning
  • a practical framework for implementation
  • expert context that explains what breaks later

That same structure helps search engines understand the page. It also helps readers decide whether the advice fits their project.

Implementation framework

Use this framework before adopting the approach described in this article.

  1. Define the user problem and the production risk.
  2. Identify the smallest reliable implementation boundary.
  3. Keep configuration, secrets, and environment-specific behavior outside the article's core logic.
  4. Add tests for the behavior that would hurt if it regressed.
  5. Document the trade-off, not only the final code.
  6. Measure the result with logs, metrics, or user-facing outcomes.
  7. Revisit the decision after real usage exposes edge cases.

The sequence is deliberately conservative. It keeps the work grounded in outcomes instead of novelty.

[IMAGE: A seven-step implementation framework with discovery, boundary design, configuration, tests, documentation, measurement, and iteration. Alt: Configuring Nginx on Ubuntu 20.04 implementation framework]

Practical comparison

Decision areaStrong approachWeak approachWhy it matters
ScopeSolve one clear problemMix unrelated concernsFocus improves testing and search intent
ArchitecturePut logic in explicit classes or documented boundariesHide behavior in templates or incidental callbacksFuture changes stay easier to review
Data flowPass prepared data into the view or endpointQuery or compute in presentation codeReduces regressions and performance surprises
TestingCover the risky behavior directlyTest only the happy pathCatches production failures earlier
DocumentationExplain trade-offs and limitsRepeat generic definitionsBuilds E-E-A-T and reader trust
OperationsTrack logs, metrics, and rollback stepsShip without measurementMakes the decision reversible

This table is intentionally practical. It gives a reviewer something to check before the implementation becomes expensive to change.

Expert workflow

Expert tip: "Treat Configuring Nginx on Ubuntu 20.04 as a system boundary. If the next developer cannot find where the decision lives, how it is tested, and when it should be avoided, the implementation is not finished."

A useful workflow is simple:

  • Start with the smallest working example.
  • Add the constraints that exist in your real project.
  • Remove anything that only demonstrates cleverness.
  • Write down the failure modes.
  • Add links to related decisions so future readers can navigate the topic cluster.

That last point matters for both humans and search systems. A single article can answer a question; a cluster proves authority.

Common mistakes

Mistake 1: Copying a pattern without its context

A pattern that works in a small demo can fail in a real application. The missing context is usually data volume, team experience, deployment process, security requirements, or observability.

Before copying the pattern, ask what assumption made it safe in the original example.

Mistake 2: Putting business logic in the wrong layer

This is the fastest way to make future debugging expensive. In Laravel, PHP, and server-rendered websites, presentation should receive prepared data, not discover rules on its own.

Keep decision logic in models, actions, services, policies, requests, jobs, or documented helpers where it can be tested directly.

Mistake 3: Optimizing for novelty instead of maintainability

Newer tools and language features can be valuable. They can also hide simple behavior behind unfamiliar syntax.

Use the option that makes the next production incident easier to understand.

Mistake 4: Publishing without a measurement plan

If the article describes a performance, SEO, security, or architecture improvement, define how success will be checked. Logs, tests, crawl diagnostics, analytics, and user behavior are all stronger than assumptions.

[IMAGE: A common-mistakes board with context loss, wrong layer, novelty bias, and missing measurement highlighted. Alt: Configuring Nginx on Ubuntu 20.04 common mistakes]

Image placeholders

  • [IMAGE: A concept diagram for Configuring Nginx on Ubuntu 20.04 with input, decision boundary, implementation, tests, and production feedback. Alt: Configuring Nginx on Ubuntu 20.04 concept diagram]
  • [IMAGE: A mobile screenshot-style checklist for Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy. Alt: Configuring Nginx on Ubuntu 20.04 mobile checklist]
  • [IMAGE: A comparison table visualization for strong versus weak implementation choices. Alt: Configuring Nginx on Ubuntu 20.04 comparison table]

Video placeholder

[VIDEO: Insert a 5-8 minute YouTube walkthrough that demonstrates the main decision, the implementation boundary, the test strategy, and the production caveats for Configuring Nginx on Ubuntu 20.04.]

Internal linking opportunities

Original Technical Deep Dive

The short version

Nginx on Ubuntu is usually built from these pieces:

JobFile or command
Install Nginxsudo apt install nginx
Site config/etc/nginx/sites-available/example.com
Enable sitesymlink into /etc/nginx/sites-enabled/
Validate configsudo nginx -t
Reload safelysudo systemctl reload nginx
TLS automationCertbot with the Nginx plugin
Reverse proxyproxy_pass to a local upstream
Logs/var/log/nginx/*access.log, /var/log/nginx/*error.log

For a simple app:

browser -> Nginx :80/:443
Nginx serves static files directly
Nginx proxies dynamic requests to 127.0.0.1:3000
Certbot manages Let's Encrypt certificates
systemd keeps Nginx and the app process running

Important current note: Ubuntu 20.04 LTS reached end of standard support on May 31, 2025. As of May 7, 2026, use Ubuntu Pro/ESM if you must keep 20.04 in production, or plan an upgrade to a supported Ubuntu LTS. The Nginx layout in this guide still matches Ubuntu 20.04-era servers.

Install Nginx

Update packages:

sudo apt update
sudo apt upgrade -y

Install Nginx:

sudo apt install -y nginx

Check service state:

systemctl status nginx --no-pager
nginx -v
sudo nginx -t

Open the firewall if UFW is active:

sudo ufw status verbose
sudo ufw allow 'Nginx Full'
sudo ufw status verbose

Or use explicit ports:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Do not open upstream app ports such as 3000, 8000, or 9000 publicly. Those should bind to 127.0.0.1 or a private network and be reached only by Nginx.

Understand Ubuntu's Nginx layout

Ubuntu uses this structure:

/etc/nginx/nginx.conf
/etc/nginx/conf.d/*.conf
/etc/nginx/sites-available/
/etc/nginx/sites-enabled/
/var/www/html
/var/log/nginx/access.log
/var/log/nginx/error.log

The main config includes site files from sites-enabled.

Check:

sudo nginx -T | sed -n '1,220p'

Do not edit generated Certbot blocks blindly. If Certbot already modified a site file, inspect it before replacing it:

sudo grep -R "managed by Certbot" -n /etc/nginx/sites-available /etc/nginx/sites-enabled 2>/dev/null

Create a web root

Use one directory per site:

sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 0755 /var/www/example.com

Create a test page:

cat <<'HTML' | sudo tee /var/www/example.com/public/index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>example.com</title>
</head>
<body>
  <h1>example.com is served by Nginx</h1>
</body>
</html>
HTML

For PHP, Laravel, Symfony, Node, Go, or another app, point root only at the public document root. Never expose the repository root, .env, vendor, storage, build artifacts, or private uploads through Nginx.

Create the first server block

Create:

sudoedit /etc/nginx/sites-available/example.com

Use:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.html index.htm;

    access_log /var/log/nginx/example.com-access.log;
    error_log /var/log/nginx/example.com-error.log warn;

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

Enable it:

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

Test locally:

curl -I -H 'Host: example.com' http://127.0.0.1
curl -s -H 'Host: example.com' http://127.0.0.1 | head

Test DNS:

dig +short example.com
dig +short www.example.com
curl -I http://example.com

Nginx selects a server by listen address and port first, then by the Host header against server_name. If nothing matches, the default server for that listen socket handles the request.

Add a catch-all default server

Create a default server that does not serve your application for random hostnames:

sudoedit /etc/nginx/sites-available/000-catch-all

Use:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _;

    return 444;
}

Enable it:

sudo ln -s /etc/nginx/sites-available/000-catch-all /etc/nginx/sites-enabled/000-catch-all
sudo nginx -t
sudo systemctl reload nginx

After TLS is configured, add a matching HTTPS default only if you understand the certificate implications. A TLS handshake happens before HTTP host routing is complete, so the default TLS server must still present a certificate.

[IMAGE: Supporting visual 1 for Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy, showing Configuring Nginx on Ubuntu 20.04 decisions, examples, and Nginx, Ubuntu, Web Server. Alt: Configuring Nginx on Ubuntu 20.04 configuring-nginx-ubuntu-20-04-virtual-hosts-ssl-reverse-proxy visual 1]

[IMAGE: Supporting visual 1 for Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy, showing Configuring Nginx on Ubuntu 20.04 decisions, examples, and Nginx, Ubuntu, Web Server. Alt: Configuring Nginx on Ubuntu 20.04 configuring-nginx-ubuntu-20-04-virtual-hosts-ssl-reverse-proxy visual 1]

Install Certbot for Let's Encrypt

Certbot currently recommends the snap package for most users.

Install snapd if needed:

sudo apt install -y snapd
sudo snap install core
sudo snap refresh core

Remove old Certbot packages if present:

sudo apt remove -y certbot python3-certbot-nginx 2>/dev/null || true

Install Certbot:

sudo snap install --classic certbot
sudo ln -sf /snap/bin/certbot /usr/local/bin/certbot

Make sure port 80 is reachable from the internet before requesting HTTP validation certificates:

sudo ufw allow 80/tcp
curl -I http://example.com

DNS must point to the server:

dig +short example.com
dig +short www.example.com

Issue a certificate

Let Certbot edit the Nginx config:

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

Or get only the certificate and edit Nginx yourself:

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

The generated certificate paths usually look like:

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem

Test renewal:

sudo certbot renew --dry-run
systemctl list-timers | grep -i certbot || true

Certbot installs a cron job or systemd timer depending on the package path. Do not write your own renewal cron until you know what is already installed.

Manual HTTPS server block

If you manage the TLS block yourself, use:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/example.com/public;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.html index.htm;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    access_log /var/log/nginx/example.com-access.log;
    error_log /var/log/nginx/example.com-error.log warn;

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

Validate:

sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com

On Ubuntu 20.04's Nginx package, listen 443 ssl http2; is the common HTTP/2 syntax. In newer Nginx versions, the current docs show:

listen 443 ssl;
http2 on;

Check your installed version before changing syntax:

nginx -v
sudo nginx -t

Add HSTS only after HTTPS is stable

After every hostname redirects correctly to HTTPS and certificates renew cleanly, you can add HSTS:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Do not add preload casually. Browser preload lists are intentionally hard to unwind. Use it only when all subdomains are permanently HTTPS-ready.

Configure a reverse proxy

Assume the application listens on localhost:

127.0.0.1:3000

The app's systemd service should bind to localhost:

APP_HOST=127.0.0.1
APP_PORT=3000

Add an upstream in the site file:

upstream example_app {
    server 127.0.0.1:3000;
    keepalive 32;
}

Then configure the HTTPS server:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    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;

    access_log /var/log/nginx/example.com-access.log;
    error_log /var/log/nginx/example.com-error.log warn;

    location / {
        proxy_pass http://example_app;
        proxy_http_version 1.1;

        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_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Test the upstream directly:

curl -I http://127.0.0.1:3000

Test through Nginx:

sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com

Your application must trust the reverse proxy before using forwarded headers for generated URLs, redirects, scheme detection, or client IPs. In Laravel, configure trusted proxies. In Express, set trust proxy appropriately. In Symfony, configure trusted proxies and trusted headers.

Proxy a path prefix

If only /api/ goes to the upstream:

location /api/ {
    proxy_pass http://example_app;
    proxy_http_version 1.1;

    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;
}

Be careful with trailing slashes in proxy_pass.

This keeps the original URI:

location /api/ {
    proxy_pass http://example_app;
}

This replaces the matching location prefix:

location /api/ {
    proxy_pass http://example_app/;
}

Use the first form unless you intentionally want URI rewriting.

Proxy WebSockets

Create a map file because map belongs in the http context, not inside a server block:

sudoedit /etc/nginx/conf.d/connection-upgrade.conf

[IMAGE: Supporting visual 2 for Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy, showing Configuring Nginx on Ubuntu 20.04 decisions, examples, and Nginx, Ubuntu, Web Server. Alt: Configuring Nginx on Ubuntu 20.04 configuring-nginx-ubuntu-20-04-virtual-hosts-ssl-reverse-proxy visual 2]

Use:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

Then in the site:

location /socket/ {
    proxy_pass http://example_app;
    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_read_timeout 3600s;
}

Validate:

sudo nginx -t
sudo systemctl reload nginx

Serve static files before proxying

For apps with a public directory:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com www.example.com;

    root /var/www/example.com/current/public;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location /assets/ {
        try_files $uri =404;
        access_log off;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        try_files $uri @app;
    }

    location @app {
        proxy_pass http://example_app;
        proxy_http_version 1.1;
        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;
    }
}

This lets Nginx serve images, CSS, JavaScript, and uploaded public files without waking the application server for every request.

Enable gzip compression

[IMAGE: Supporting visual 2 for Configuring Nginx on Ubuntu 20.04: Virtual Hosts, SSL & Reverse Proxy, showing Configuring Nginx on Ubuntu 20.04 decisions, examples, and Nginx, Ubuntu, Web Server. Alt: Configuring Nginx on Ubuntu 20.04 configuring-nginx-ubuntu-20-04-virtual-hosts-ssl-reverse-proxy visual 2]

Create:

sudoedit /etc/nginx/conf.d/gzip.conf

Use:

gzip on;
gzip_comp_level 5;
gzip_min_length 1000;
gzip_vary on;
gzip_proxied no-cache no-store private expired auth;
gzip_types
    text/plain
    text/css
    text/xml
    application/json
    application/javascript
    application/xml
    application/rss+xml
    image/svg+xml;

Validate:

sudo nginx -t
sudo systemctl reload nginx
curl -H 'Accept-Encoding: gzip' -I https://example.com

Nginx compresses text/html by default when gzip is enabled. Add MIME types deliberately. Do not gzip already-compressed assets like JPEG, PNG, WebP, MP4, zip, or gzipped files.

Tune upload and timeout limits

Set request body size for apps that accept uploads:

client_max_body_size 20m;

For a specific endpoint:

location /uploads/ {
    client_max_body_size 100m;
    proxy_pass http://example_app;
}

Proxy timeout defaults can be too short for slow reports, exports, or long-polling endpoints:

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Prefer fixing slow application endpoints before increasing timeouts globally.

Add basic security headers

Start with low-risk headers:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Add Content Security Policy only after inventorying scripts, styles, images, frames, fonts, and third-party integrations:

add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'" always;

Do not paste a strict CSP into production and hope. Test it in report-only mode first if the app has third-party scripts or inline assets.

Use a deployment-friendly release path

For application releases, point Nginx at a stable symlink:

/var/www/example.com/releases/2026-05-07-120000/public
/var/www/example.com/current -> /var/www/example.com/releases/2026-05-07-120000

Nginx config:

root /var/www/example.com/current/public;

Deploy flow:

sudo nginx -t
ln -sfn /var/www/example.com/releases/2026-05-07-120000 /var/www/example.com/current
sudo systemctl reload nginx

If only the app code changes and Nginx config does not, you usually do not need to reload Nginx after switching the symlink. Reload only when Nginx config, certificates, or included files changed.

Validate every config change

Use this sequence:

sudo nginx -t
sudo systemctl reload nginx
systemctl status nginx --no-pager
sudo journalctl -u nginx -n 80 --no-pager

Dump the active config when debugging include order:

sudo nginx -T | less

Check open sockets:

sudo ss -lntp | grep nginx

Check response routing:

curl -I -H 'Host: example.com' http://127.0.0.1
curl -I https://example.com
curl -I https://www.example.com

Log what matters

Per-site logs make incidents easier:

access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log warn;

For reverse proxies, include upstream timing:

log_format upstream_timing '$remote_addr - $host "$request" '
                           'status=$status bytes=$body_bytes_sent '
                           'request_time=$request_time '
                           'upstream_status=$upstream_status '
                           'upstream_response_time=$upstream_response_time '
                           'upstream_addr=$upstream_addr';

Put log_format in the http context, for example /etc/nginx/conf.d/log-format.conf, then use:

access_log /var/log/nginx/example.com-access.log upstream_timing;

Check logs:

sudo tail -f /var/log/nginx/example.com-access.log
sudo tail -f /var/log/nginx/example.com-error.log

Ubuntu's package usually installs logrotate rules for Nginx. Confirm:

cat /etc/logrotate.d/nginx

Troubleshooting

Nginx will not reload:

sudo nginx -t
sudo journalctl -u nginx -n 120 --no-pager

Wrong site is served:

sudo nginx -T | grep -n "server_name\\|listen"
curl -I -H 'Host: example.com' http://127.0.0.1

Check:

site is linked in sites-enabled
default site is not catching the hostname
server_name is exact
DNS points to this server
IPv6 AAAA records point to this server if IPv6 is enabled

Certbot fails HTTP validation:

dig +short example.com
curl -I http://example.com/.well-known/acme-challenge/test
sudo ufw status verbose
sudo tail -n 100 /var/log/nginx/example.com-error.log

Check:

port 80 open in cloud firewall and UFW
DNS A/AAAA records correct
HTTP server block does not redirect challenge paths incorrectly
no CDN proxy is interfering

502 Bad Gateway:

sudo systemctl status nginx --no-pager
sudo ss -lntp | grep -E '3000|8000|9000'
curl -I http://127.0.0.1:3000
sudo tail -n 100 /var/log/nginx/example.com-error.log

Likely causes:

upstream app is down
wrong upstream port
app binds to a different interface
systemd service crashed
Nginx cannot resolve an upstream hostname
proxy timeout hit

Redirect loop:

curl -IL https://example.com

Check:

app trusts X-Forwarded-Proto
only one layer forces HTTP to HTTPS
load balancer and Nginx agree on scheme
canonical host redirect does not fight app redirect

Assets return 404:

sudo -u www-data test -r /var/www/example.com/current/public/assets/app.css && echo readable
sudo namei -l /var/www/example.com/current/public/assets/app.css

Check:

root points to public directory
release symlink points to the current release
file permissions allow Nginx read access
try_files path matches built asset location

Production checklist

Before calling the host ready:

[ ] Ubuntu 20.04 has ESM enabled or an upgrade plan.
[ ] Nginx config validates with nginx -t.
[ ] Only ports 80 and 443 are public unless intentionally documented.
[ ] HTTP redirects to HTTPS.
[ ] Certificates renew with certbot renew --dry-run.
[ ] HTTP/2 syntax matches the installed Nginx version.
[ ] Static files are served directly by Nginx.
[ ] Upstream app binds to localhost or a private interface.
[ ] Forwarded headers are configured in Nginx and trusted by the app.
[ ] Upload limits are explicit.
[ ] Gzip is enabled for useful text formats only.
[ ] Logs are per-site and rotated.
[ ] Default server behavior is intentional.
[ ] Cloud firewall and UFW rules match.

Nginx is not complicated when each concern stays in the right layer: server blocks route hostnames, Certbot manages certificates, Nginx terminates TLS and serves static files, and the upstream application handles business logic.

FAQ

What is Configuring Nginx on Ubuntu 20.04?

Configuring Nginx on Ubuntu 20.04 is a practical web server topic that should be evaluated through implementation scope, production risk, testing, documentation, and long-term maintainability.

When should a team use Configuring Nginx on Ubuntu 20.04?

Use Configuring Nginx on Ubuntu 20.04 when it solves a real project constraint, improves clarity, or reduces operational risk. Avoid it when it only adds novelty or hides behavior from future maintainers.

What is the biggest risk with Configuring Nginx on Ubuntu 20.04?

The biggest risk is copying a pattern without its context. Production systems need clear boundaries, rollback options, tests, and observability before a technique becomes dependable.

How do you test Configuring Nginx on Ubuntu 20.04?

Test the smallest unit that owns the behavior, then add integration coverage for the path users or systems actually rely on. Include failure cases, configuration differences, and regression checks.

How does Configuring Nginx on Ubuntu 20.04 affect SEO and AI search visibility?

It improves visibility when the article gives a direct answer, expert context, structured headings, internal links, trustworthy references, and FAQ content that matches the visible page.

Conclusion

Configuring Nginx on Ubuntu 20.04 is worth doing when the implementation improves clarity, reliability, or delivery speed. It is not worth doing when it hides ownership, increases operational risk, or makes the system harder to explain.

Use the framework above as a review checklist. Then connect this topic to the rest of the project documentation so readers can move from concept to implementation without losing context.

Top