NGINX can put a website on a remote server’s public IP address. Start with a static page: update Ubuntu’s package list, install NGINX, and open the server’s IP address in a browser. The default page confirms that the web server is running. section 3.2
Replace the default page
NGINX serves static HTML from /var/www/html by default. Remove index.nginx-debian.html, create /var/www/html/index.html, then refresh the server’s IP address to see your page. This uses the existing setup; no NGINX configuration change is needed. section 3.2.2
When an application generates responses rather than serving a fixed HTML file, NGINX can sit in front of it as a reverse proxy: it receives a request and forwards it to an application it can reach. For example, one server can send requests for / to a Python application running through gunicorn on localhost:8888, and requests for /js to a Node.js Express application on localhost:3000. section 3.6 section 3.6.1
Listing 3.34 NGINX reverse proxy configuration
cat <<EOF > /etc/nginx/sites-available/roadtok8s
server {
listen 80;
server_name localhost;
location / {#1
proxy_pass http://localhost:8888;
}
location /js/ {#2
proxy_pass http://localhost:3000/;
}
}
EOF
The server listens on port 80; each location selects a request path, and proxy_pass names the application that receives it. section 3.6.1
To activate that configuration, place it in /etc/nginx/sites-available/, link it into /etc/nginx/sites-enabled/, remove the enabled default site, and restart NGINX. Check the public routes in a browser. Routing requests through NGINX does not itself close the applications’ ports to direct access; a firewall addresses that separate problem. section 3.6.1
The same installation can be automated on a remote Ubuntu server with a GitHub Actions workflow that connects over SSH and runs apt update and apt install nginx -y. Keep the SSH private key and server IP in workflow secrets rather than in the repository. section 4.1.5
NGINX can also run as a Docker container instead of being installed directly on Ubuntu. Run it detached to keep it in the background, publish its port to make it reachable through the host, and use docker ps to find the running container. This provides a small deployment test before containerizing the Python and Node.js applications. section 7.0 section 7.1.4
Listing 7.3 Stopping containers and publishing port
docker run -d --restart always --name my-nginx -p 80:80 nginx
This command runs the NGINX container in the background, names it, sets a restart policy, and publishes host port 80 to container port 80. section 7.1.4
When several application instances can serve the same request, an NGINX upstream lists their destinations and proxy_pass forwards requests to that group. This extends path-based routing into load balancing. The destinations can use different local ports or IP addresses; Docker Compose services can also provide multiple destinations. Load balancing choices are introduced, but not explored in depth. section 3.6 section 7.4.1
Listing 7.16 NGINX load balancing basics
upstream myapp {
server localhost:8080;
server localhost:8081;
server my-ip-address:80;
server my-other-ip-address:80;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://myapp;
}
location /api {
proxy_pass http://localhost:3000;
}
}
Compare the upstream group used for / with the separate /api route: NGINX can distribute one path across destinations while sending another to a single application. section 7.4.1


