A production-like Nginx setup on Linux featuring:
- Static website hosted from
/var/www/secure-app - HTTPS using a self-signed SSL certificate (OpenSSL, 365 days)
- HTTP → HTTPS automatic redirect
- Reverse proxy to a Node.js backend on port
3000
sudo apt update
sudo apt install -y nginx opensslsudo mkdir -p /var/www/secure-app
sudo vi /var/www/secure-app/index.html
sudo chown -R www-data:www-data /var/www/secure-app
sudo chmod -R 755 /var/www/secure-appThe page shows “Secure Server Running via Nginx” once Nginx is serving it.
sudo mkdir -p /etc/nginx/sslsudo openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt \
-subj "/C=IN/ST=State/L=City/O=SecureApp/OU=Dev/CN=localhost"sudo chmod 600 /etc/nginx/ssl/selfsigned.keyFiles produced:
/etc/nginx/ssl/selfsigned.crt/etc/nginx/ssl/selfsigned.key
sudo vi /etc/nginx/sites-available/secure-app
sudo ln -sf /etc/nginx/sites-available/secure-app /etc/nginx/sites-enabled/secure-app
sudo rm -f /etc/nginx/sites-enabled/default# /etc/nginx/sites-available/secure-app
# Nginx config: HTTPS with self-signed SSL + reverse proxy to backend on :3000
# ---------- HTTP: redirect everything to HTTPS ----------
server {
listen 80;
listen [::]:80;
server_name _;
return 301 https://$host$request_uri;
}
# ---------- HTTPS: serve static site + reverse proxy ----------
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name _;
ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /var/www/secure-app;
index index.html;
# Static site
location / {
try_files $uri $uri/ =404;
}
# Reverse proxy to backend on port 3000
location /api/ {
proxy_pass http://127.0.0.1:3000/;
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;
}
}Key points:
- Port 80 →
301redirect tohttps://$host$request_uri - Port 443 → SSL using
ssl_certificate+ssl_certificate_key root /var/www/secure-appandindex index.html/api/is reverse-proxied tohttp://127.0.0.1:3000/
sudo apt install -y nodejssudo mkdir -p /var/www/backend
sudo nano /var/www/backend/server.js
sudo chown -R ubuntu:ubuntu /var/www/backendconst http = require("http");
const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify(
{
message: "Hello from backend!",
path: req.url,
host: req.headers["host"],
realIp: req.headers["x-real-ip"] || null,
forwardedFor: req.headers["x-forwarded-for"] || null,
timestamp: new Date().toISOString(),
},
null,
2,
),
);
});
server.listen(PORT, "127.0.0.1", () => {
console.log(`Backend listening on http://127.0.0.1:${PORT}`);
});The backend echoes back the Host and X-Real-IP headers so you can
verify Nginx is correctly passing them through.
Deploy the unit file and enable it:
sudo vi /etc/systemd/system/backend.service
sudo systemctl daemon-reload
sudo systemctl enable --now backend
sudo systemctl status backend --no-pagerUnit contents (backend/backend.service):
[Unit]
Description=Secure App Backend (Node.js on port 3000)
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/var/www/backend
ExecStart=/usr/bin/node /var/www/backend/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetAfter systemctl enable --now backend, the service is active and Node.js
is listening on 127.0.0.1:3000:
location /api/ {
proxy_pass http://127.0.0.1:3000/;
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;
}sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pagerExpected:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
curl -I http://localhostExpected (truncated):
HTTP/1.1 301 Moved Permanently
Location: https://localhost/
In a browser, open https://<server-ip>/. Because the certificate is
self-signed the browser shows Not Secure — that is expected — but
the connection is HTTPS and the page renders:
curl -k https://localhost/api/Expected (example):
{
"message": "Hello from backend!",
"path": "/",
"host": "localhost",
"realIp": "::1",
"forwardedFor": "::1",
"timestamp": "2026-04-21T17:39:10.708Z"
}The non-null realIp field confirms Nginx is passing X-Real-IP
through to the backend.
# Packages
sudo apt update && sudo apt install -y nginx openssl nodejs
# Web root + static page
sudo mkdir -p /var/www/secure-app
sudo nano /var/www/secure-app/index.html
# SSL (365 days)
sudo mkdir -p /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt \
-subj "/C=IN/ST=State/L=City/O=SecureApp/OU=Dev/CN=localhost"
sudo chmod 600 /etc/nginx/ssl/selfsigned.key
# Nginx site
sudo vi /etc/nginx/sites-available/secure-app
sudo ln -sf /etc/nginx/sites-available/secure-app /etc/nginx/sites-enabled/secure-app
sudo rm -f /etc/nginx/sites-enabled/default
# Backend
sudo mkdir -p /var/www/backend
sudo vi /var/www/backend/server.js
sudo vi /etc/systemd/system/backend.service
sudo systemctl daemon-reload
sudo systemctl enable --now backend
# Test + reload
sudo nginx -t
sudo systemctl reload nginx














