Nginx on Localhost
Nginx (pronounced "engine-x") is a high-performance web server and reverse proxy. In local development, it's most useful as a reverse proxy that sits in front of other services — letting you access a Node.js app at localhost:3000 via a clean domain like myapp.local, or routing multiple projects to different ports from a single entry point. Nginx also serves static files extremely efficiently and handles PHP applications via PHP-FPM.
Install Nginx
# Ubuntu / Debian
sudo apt update && sudo apt install nginx
sudo systemctl start nginx
# macOS (Homebrew)
brew install nginx
brew services start nginx # Starts on port 8080 by default on Mac
# Windows — download from nginx.org and run nginx.exe
# Or use Laragon which bundles Nginx
# Docker
docker run -d -p 80:80 --name nginx nginx:alpine
Config File Structure
# /etc/nginx/nginx.conf (simplified)
http {
include /etc/nginx/sites-enabled/*; # Ubuntu
# or: include /etc/nginx/conf.d/*.conf; # CentOS/Docker
}
# /etc/nginx/sites-available/myapp.conf
server {
listen 80;
server_name myapp.local;
root /var/www/myapp/public;
index index.html index.php;
}
On Ubuntu, enable a config with: sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/ then sudo nginx -s reload.
PHP-FPM Setup
Nginx doesn't run PHP natively — it passes PHP requests to a PHP-FPM process. A typical PHP site configuration:
server {
listen 80;
server_name myapp.local;
root /var/www/myapp/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Reverse Proxy — Node.js, Python, etc.
The most common Nginx use case in local development: proxy a running backend app so you can reach it at a clean URL instead of typing a port number:
# Proxy localhost:3000 (Node.js app) to myapp.local
server {
listen 80;
server_name myapp.local;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Add 127.0.0.1 myapp.local to your /etc/hosts (or C:\Windows\System32\drivers\etc\hosts) to make myapp.local resolve locally.
Multiple Projects — Different Domains
# /etc/nginx/sites-available/api.local.conf
server {
listen 80;
server_name api.local;
location / { proxy_pass http://localhost:8000; }
}
# /etc/nginx/sites-available/frontend.local.conf
server {
listen 80;
server_name frontend.local;
location / { proxy_pass http://localhost:5173; }
}
HTTPS Locally with mkcert
For testing HTTPS locally (required for some browser APIs and service workers), use mkcert to generate trusted local certificates:
# Install mkcert (macOS)
brew install mkcert
mkcert -install # Adds CA to system trust store
# Generate cert for your local domain
mkcert myapp.local
# Nginx HTTPS config
server {
listen 443 ssl;
server_name myapp.local;
ssl_certificate /path/to/myapp.local.pem;
ssl_certificate_key /path/to/myapp.local-key.pem;
location / { proxy_pass http://localhost:3000; }
}
server {
listen 80;
server_name myapp.local;
return 301 https://$host$request_uri;
}
Nginx vs Apache — Quick Comparison
| Feature | Nginx | Apache |
|---|---|---|
| Architecture | Event-driven, async (efficient under load) | Process/thread-based (simpler) |
| PHP | Via PHP-FPM (extra config step) | mod_php (built-in, easier) |
| .htaccess | Not supported — all config in server blocks | Supported per-directory |
| Static files | Faster | Good |
| Reverse proxy | Clean, widely used | Possible with mod_proxy |
| Windows support | Limited | Good (XAMPP, WAMP) |
Troubleshooting
| Problem | Fix |
|---|---|
| Port 80 already in use | Apache, another Nginx, or a Docker container is on port 80 — stop it or change Nginx to port 8080 |
| Config test before reload | sudo nginx -t — shows syntax errors before applying |
| 502 Bad Gateway | The upstream app (Node, PHP-FPM) isn't running or isn't on the expected port |
| 403 Forbidden on static files | File permission issue — Nginx worker process needs read access to the web root |
| Reload vs restart | nginx -s reload applies config without dropping connections. Use restart only if reload fails. |