Skip to main content

Install Nginx

Verify the version:

Firewall

UFW ships with predefined app profiles. 'Nginx Full' opens both port 80 (HTTP) and port 443 (HTTPS). There are also 'Nginx HTTP' (80 only) and 'Nginx HTTPS' (443 only) if you want to be more restrictive — but since SSL gets added later via Certbot, both ports need to be open, so Full is correct here.
Opens port 22 (SSH). This step is critical — if UFW is enabled without explicitly allowing SSH first, you’ll lock yourself out of the VPS the moment the firewall activates, since UFW defaults to denying all incoming connections.
Turns the firewall on. By default ufw enable asks for a y/n confirmation (since it could disrupt active SSH sessions) — --force skips that prompt, which is useful for scripting/automation but means you must be 100% sure SSH is already allowed before running it.
Confirm the rules took effect:

Default catch-all redirect (HTTP → HTTPS)

Before setting up per-app server blocks, add a catch-all block that handles any request that doesn’t match a known domain (raw IP hits, typo’d hostnames, unmatched Host headers) and forces it onto HTTPS. /etc/nginx/sites-available/default:
Marks this block as the fallback for port 80 — any request whose Host header doesn’t match a server_name in your other configs lands here instead of silently hitting the first server block in load order. Assigned inline on the listen line itself; no separate config or setup needed.
The underscore is a catch-all placeholder that matches nothing by name — Nginx relies on default_server (not this line) to route unmatched traffic here.
308 preserves the original HTTP method and body on redirect (unlike the older 301, which some clients silently downgrade to GET) — important if a client sends a POST and you don’t want it dropped.
Only one server block per listen port/address can have default_server. If it’s already set elsewhere, remove it there first or nginx -t will fail.

Example reverse proxy config

/etc/nginx/sites-available/j-optic-backend:
listen [::]:80; opens the same block on IPv6. If your VPS provider doesn’t route IPv6 to you yet, this line is harmless — but including it now avoids re-editing every site config later if IPv6 gets enabled. Once Certbot adds SSL, it’ll auto-insert listen 443 ssl; but won’t always add the IPv6 line — add listen [::]:443 ssl; manually alongside it.

Enable the sites

Repeat the per-app block pattern for each app (j-optic, j-store) with a different server_name and proxy_pass port. Only the catch-all default block needs to exist once.

Useful checks