Kritim Yantra
Sep 07, 2026
The first time you open an NGINX configuration file, it doesn't look particularly friendly.
You might see something like:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
}
}
Then somebody tells you:
"Just paste this into NGINX and restart it."
It works.
But you still don't know why it works.
Then something breaks.
Suddenly you are searching questions such as:
server?location / mean?proxy_pass?fastcgi_pass instead?location block break another?That's where learning NGINX properly becomes valuable.
The good news is that NGINX becomes much easier once you stop thinking of it as a collection of mysterious configuration commands.
The main idea is simple:
A request arrives. NGINX decides where it should go and how it should be handled.
Almost everything else builds on that idea.
This guide will take you from that basic mental model to more advanced NGINX concepts used in real production systems.
NGINX is commonly used as a:
A request might arrive like this:
User's Browser
|
v
NGINX
|
v
Your Application
Or NGINX might return a static file directly:
Browser
|
v
NGINX
|
+----> image.jpg
+----> style.css
+----> app.js
Or perhaps distribute requests between several application servers:
+--> App Server 1
|
Browser --> NGINX ---+--> App Server 2
|
+--> App Server 3
NGINX officially supports serving static content, proxying requests to other servers, FastCGI applications such as PHP, and HTTP load balancing.
Once you understand these different jobs, the configuration starts making much more sense.
Before learning individual directives, remember this:
REQUEST
|
v
Which IP/Port received it?
|
v
Which server block matches?
|
v
Which location matches?
|
v
What should NGINX do?
|
+--> Serve a file
|
+--> Redirect
|
+--> Reverse proxy
|
+--> Send to PHP-FPM
|
+--> Return an error
If you understand this flow, you already understand a large portion of NGINX.
Let's build it step by step.
A simplified NGINX configuration often looks like this:
worker_processes auto;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name example.com;
location / {
root /var/www/example;
index index.html;
}
}
}
There are different configuration contexts.
Think of them as containers.
Main
│
├── events
│
└── http
│
└── server
│
└── location
This hierarchy is extremely important.
A directive is basically an instruction given to NGINX.
For example:
listen 80;
means:
Listen for requests on port 80.
Another example:
server_name example.com;
means:
This server configuration should handle requests for example.com.
Another:
root /var/www/example;
means:
Look for website files inside this directory.
You don't need to memorize hundreds of NGINX directives.
Instead, learn the important ones and understand where they can be used.
httpThe http block contains configuration related to HTTP and HTTPS traffic.
For example:
http {
server {
listen 80;
server_name example.com;
}
}
Most web-server configuration eventually lives somewhere inside this context.
You may also configure things such as:
inside or under the http context.
server BlockThe server block represents a virtual web server.
Example:
server {
listen 80;
server_name example.com;
root /var/www/example;
}
Think of it like this:
"When traffic for this website arrives, use these rules."
This is how one NGINX installation can host multiple websites.
For example:
server {
listen 80;
server_name website-one.com;
root /var/www/website-one;
}
server {
listen 80;
server_name website-two.com;
root /var/www/website-two;
}
Both websites use the same NGINX server.
But each domain gets different configuration.
NGINX first considers the listening address and port, then uses the request's host information to select the appropriate server configuration.
listenYou will see this everywhere:
listen 80;
Port 80 is normally used for HTTP.
For HTTPS, you'll commonly see:
listen 443 ssl;
So:
80 = HTTP
443 = HTTPS
You can also listen on other ports.
For example:
listen 8080;
Then you could access the server using:
http://example.com:8080
server_nameConsider:
server_name example.com www.example.com;
This tells NGINX which hostnames belong to this server configuration.
For example:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example;
}
A request for:
example.com
or:
www.example.com
can be handled by this block.
NGINX also supports exact, wildcard, and regular-expression server names, although beginners usually only need exact domain names initially.
Pro Tip: Don't jump into wildcard and regex server names until you actually need them. Exact names are easier to understand and troubleshoot.
rootSuppose your files are stored here:
/var/www/example/
You can tell NGINX:
root /var/www/example;
Then imagine the browser requests:
/about.html
NGINX can look for:
/var/www/example/about.html
That's one of NGINX's simplest jobs:
mapping URLs to files on disk.
indexWhat happens when someone requests:
https://example.com/
instead of:
https://example.com/index.html
That's where index becomes useful.
index index.html index.htm;
For a PHP application, you may see:
index index.php index.html;
NGINX will attempt to use one of those files as the directory index.
locationThis is probably the NGINX concept that confuses beginners the most.
Consider:
location / {
root /var/www/example;
}
The location block tells NGINX:
When a request URL matches this pattern, use these rules.
For example:
location /images/ {
root /var/www/example;
}
could handle requests beginning with:
/images/
The official NGINX beginner guide explains that when multiple prefix locations match, NGINX selects the longest matching prefix before considering applicable regular-expression locations.
Imagine this configuration:
location / {
# General website
}
location /api/ {
# API requests
}
location /images/ {
# Image requests
}
Now imagine these requests:
/
/about
/api/users
/api/products
/images/logo.png
You can roughly visualize them as:
/ --> location /
/about --> location /
/api/users --> location /api/
/api/products --> location /api/
/images/logo.png --> location /images/
This concept becomes extremely powerful.
location MatchingYou may encounter:
location /images/ {
}
location = / {
}
location ^~ /static/ {
}
location ~ \.php$ {
}
location ~* \.(jpg|png|gif)$ {
}
Let's simplify them.
location /api/ {
}
Matches URLs beginning with:
/api/
location = /health {
return 200 "OK";
}
This matches exactly:
/health
but not:
/health/test
location ~ \.php$ {
}
Often used to match PHP files.
location ~* \.(jpg|jpeg|png|gif)$ {
}
This could match:
photo.jpg
photo.JPG
photo.Png
NGINX documents ~ for case-sensitive regular expressions, ~* for case-insensitive expressions, = for exact matching, and ^~ for a prefix that should prevent later regex-location checks when that prefix wins.
Location matching can get surprisingly complicated.
As a beginner, remember:
^~ can prevent regex locations from overriding the selected prefix.That's enough to get started.
Come back to the full location-selection rules when your configuration becomes more complicated.
try_filesThis directive is extremely useful for modern applications.
Consider:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Think of it like:
Does the requested file exist?
|
Yes
|
Serve it directly
OR
Does the directory exist?
|
Yes
|
Use it
OR
Send request to index.php
This pattern is common with frameworks where a single application entry point handles routing.
For example:
/products/123
may not actually correspond to:
/products/123
on the filesystem.
Instead, the application itself decides what /products/123 means.
That's why try_files is especially useful with framework routing.
Suppose your application has:
CSS
JavaScript
Images
Fonts
Downloads
NGINX can serve these directly.
For example:
location /static/ {
root /var/www/example;
}
Instead of asking your PHP, Node.js, Python, or Java application to read every image and stylesheet, NGINX can handle those files itself.
A useful mental model is:
Static Request
|
v
NGINX
|
v
Return File
while dynamic content might look like:
Dynamic Request
|
v
NGINX
|
v
Application
|
v
NGINX
|
v
Browser
This is one of the most important NGINX concepts you'll ever learn.
Imagine your Node.js application is running on:
127.0.0.1:3000
Without NGINX, users might theoretically have to access:
http://example.com:3000
Instead, NGINX receives normal web traffic:
https://example.com
and internally sends the request to:
http://127.0.0.1:3000
This is called a reverse proxy.
The flow becomes:
Internet
|
v
NGINX :443
|
v
Application :3000
NGINX's proxy module is specifically designed to pass requests to another server.
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Now:
User
|
v
example.com
|
v
NGINX
|
v
127.0.0.1:3000
The user doesn't need to know that your actual application runs on port 3000.
A more realistic reverse proxy configuration often includes headers:
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
Why?
Because otherwise your backend application may not know useful information about the original request.
For example:
Original visitor IP
Original hostname
Original protocol
The official proxy-module example demonstrates passing the original host and client IP information with proxy_set_header.
These names confuse beginners.
Here's the easiest way I remember them.
Works on behalf of the client.
Client
|
Proxy
|
Internet
Works in front of the server.
Internet
|
Reverse Proxy
|
Application
NGINX is commonly used as the second one.
This is especially important if you're a PHP developer.
NGINX does not normally execute PHP code itself.
Instead, PHP runs through something such as PHP-FPM.
The architecture looks like:
Browser
|
v
NGINX
|
v
PHP-FPM
|
v
PHP Application
NGINX sends PHP requests to the FastCGI server.
The official NGINX documentation describes PHP applications as a common FastCGI use case and uses fastcgi_pass to send requests to the FastCGI application server.
You might see:
server {
listen 80;
server_name example.com;
root /var/www/example/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
}
}
The exact PHP-FPM socket path varies depending on your operating system and PHP installation.
But conceptually:
HTML/CSS/Image
|
v
NGINX
|
v
Serve directly
while:
PHP Request
|
v
NGINX
|
v
PHP-FPM
|
v
PHP executes
proxy_pass vs fastcgi_passThis distinction becomes much easier once you know what sits behind NGINX.
For something like Node.js:
proxy_pass http://127.0.0.1:3000;
For something speaking FastCGI, such as PHP-FPM:
fastcgi_pass unix:/run/php/php-fpm.sock;
Think:
HTTP Application
=
proxy_pass
PHP-FPM / FastCGI
=
fastcgi_pass
Don't blindly replace one with the other.
They communicate using different protocols.
Now we're moving beyond beginner concepts.
NGINX generally uses a master process and worker processes.
A simplified representation:
NGINX Master Process
|
+--> Worker 1
|
+--> Worker 2
|
+--> Worker 3
|
+--> Worker 4
The master manages configuration and worker processes.
Workers handle actual request processing.
The official beginner guide notes that worker-process counts can be fixed or automatically adjusted based on available CPU cores.
You will often see:
worker_processes auto;
For many systems, letting NGINX determine an appropriate worker count is a sensible starting point.
events and worker_connectionsYou may see:
events {
worker_connections 1024;
}
This controls connection-processing behavior for NGINX workers.
Don't make the beginner mistake of assuming:
4 workers × 1024 = exactly 4096 website visitors
Real connection capacity depends on more factors because connections can include client and upstream connections, operating-system limits, keepalive behavior, application architecture, and more.
The lesson is:
Don't copy random "high performance" NGINX tuning values without understanding the workload.
Defaults and simple configurations are often perfectly adequate while learning.
NGINX includes useful variables.
You've already seen some:
$host
$remote_addr
$scheme
$uri
$query_string
$document_root
These allow configurations to respond dynamically to request information.
For example:
proxy_set_header Host $host;
uses the request host.
And:
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
constructs the PHP script path passed to FastCGI. The NGINX FastCGI documentation specifically describes SCRIPT_FILENAME as important for identifying the PHP script being executed.
$uri and the Original RequestAs you become more advanced, you'll discover that NGINX can internally rewrite and normalize requests.
Variables such as:
$uri
and:
$request_uri
aren't always interchangeable.
This becomes important with:
You don't need to master these differences on day one.
Just remember:
NGINX variables represent different pieces or stages of the request. Always verify which one a directive expects.
Redirects are easy.
For example:
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
This can redirect HTTP traffic to HTTPS.
The flow:
http://example.com/page
|
v
NGINX
|
v
301 Redirect
|
v
https://example.com/page
301 represents a permanent redirect.
For temporary redirects, you may use codes such as 302 depending on your situation.
On a production website, HTTPS is no longer something you should treat as optional.
A simplified HTTPS configuration looks like:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/private-key.pem;
}
NGINX requires the HTTPS listener to enable SSL/TLS and references the server certificate and private key through directives such as ssl_certificate and ssl_certificate_key.
The private key is sensitive.
Never commit private TLS keys to Git or expose them publicly.
A production setup might conceptually look like:
Port 80
|
v
Redirect to HTTPS
|
v
Port 443
|
v
TLS handled by NGINX
|
v
Application
This is another reason reverse proxies are useful.
Your application might internally run:
http://127.0.0.1:3000
while users access:
https://example.com
NGINX handles the public HTTPS connection.
When debugging NGINX, logs are your friend.
A typical access log records requests such as:
GET /products HTTP/1.1
GET /style.css HTTP/1.1
POST /login HTTP/1.1
Think of an access log as:
"What requests reached my server?"
It can help answer questions like:
The error log answers a different question:
"What went wrong while NGINX handled the request?"
You might discover:
Permission denied
Connection refused
No such file or directory
or an upstream failure.
When your website returns:
502 Bad Gateway
don't randomly edit the configuration.
Check the logs.
A connection-refused message may immediately tell you that NGINX can't reach your application server.
This error becomes familiar to backend developers.
Suppose NGINX contains:
proxy_pass http://127.0.0.1:3000;
but your Node.js application isn't running.
NGINX receives the user's request.
Then it tries:
127.0.0.1:3000
Nobody is listening there.
Result:
502 Bad Gateway
The same idea applies if NGINX can't reach PHP-FPM.
So when you see a 502 error, ask:
That's much more productive than immediately reinstalling NGINX.
This is one habit I strongly recommend learning immediately.
After modifying NGINX configuration, run:
sudo nginx -t
NGINX officially provides -t to check configuration syntax and verify that referenced files can be opened.
If everything is correct, you'll get a successful configuration test.
Then reload NGINX.
On many Linux systems using systemd:
sudo systemctl reload nginx
Or NGINX itself supports:
nginx -s reload
Its reload mechanism validates the new configuration and, when successful, starts workers using the new configuration while gracefully shutting down the old workers. If applying the new configuration fails, NGINX continues with the previous configuration.
Pro Tip: Develop this habit:
sudo nginx -t
then:
sudo systemctl reload nginx
Not:
edit → restart → hope.
These are not conceptually the same thing.
A reload tells NGINX to reread its configuration while handling the transition gracefully.
A full restart stops and starts the service.
For routine configuration changes, a reload is commonly preferable.
Think:
Configuration Change
|
v
nginx -t
|
v
Valid?
/ \
No Yes
| |
Fix it Reload
Now we're moving into more advanced territory.
Instead of writing:
proxy_pass http://127.0.0.1:3000;
you can create an upstream group:
upstream backend {
server 127.0.0.1:3000;
}
Then:
location / {
proxy_pass http://backend;
}
At first, that may seem unnecessary.
But now imagine multiple application servers.
upstream backend {
server 10.0.0.11:3000;
server 10.0.0.12:3000;
server 10.0.0.13:3000;
}
Suddenly the purpose becomes obvious.
With multiple upstream servers:
upstream backend {
server app1:3000;
server app2:3000;
server app3:3000;
}
and:
location / {
proxy_pass http://backend;
}
NGINX can distribute requests between them.
The default HTTP load-balancing method is round-robin. NGINX also supports methods such as least-connected and IP-based hashing.
The architecture becomes:
+--> App 1
|
Users --> NGINX+--> App 2
|
+--> App 3
Now we're moving from:
"Serve my website"
to:
"Distribute application traffic across infrastructure."
Consider:
upstream backend {
server app1:3000;
server app2:3000;
server app3:3000;
}
Without another balancing method configured, NGINX uses round-robin distribution.
Very roughly:
Request 1 -> App 1
Request 2 -> App 2
Request 3 -> App 3
Request 4 -> App 1
This is a simplified mental model, but it helps explain the idea.
Some requests take longer than others.
Instead of simple round-robin distribution, you can use:
upstream backend {
least_conn;
server app1:3000;
server app2:3000;
}
NGINX then favors the server with fewer active connections, taking configured weights into account.
This can be useful when request durations vary significantly.
Maybe one server is much more powerful than another.
You can influence distribution using weights:
upstream backend {
server app1:3000 weight=3;
server app2:3000;
}
The server with the larger weight receives a larger share of requests under the applicable balancing algorithm.
This is useful when backend servers don't have equal capacity.
Suppose somebody starts sending hundreds of requests per second to:
/login
or:
/api/search
NGINX can help control request rates before excessive traffic reaches your application.
A simple example:
limit_req_zone $binary_remote_addr
zone=api_limit:10m
rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20;
}
}
NGINX's request-limit module can limit processing based on a key such as the client's IP address and uses a leaky-bucket approach to control request rates and bursts.
Conceptually:
Normal Requests
|
v
NGINX
|
v
Application
Too Many Requests
|
v
NGINX
|
X
Rate Limit
This distinction matters.
NGINX rate limiting can protect application endpoints from certain forms of excessive traffic and abuse.
But don't think:
"I added
limit_req, so my server can survive every DDoS attack."
Large-scale network attacks require protection at infrastructure and network levels too.
Use rate limiting as one layer of protection, not as magic armor.
Static content such as:
HTML
CSS
JavaScript
JSON
SVG
can often benefit from compression.
NGINX supports gzip response compression. Its documentation notes that compression can substantially reduce the amount of transmitted data, depending on the content.
A basic example might look like:
gzip on;
gzip_types
text/plain
text/css
application/javascript
application/json
application/xml;
Don't blindly set:
gzip_comp_level 9;
assuming a larger number automatically means a better website.
Higher compression levels can consume more CPU.
Performance tuning is about trade-offs.
Imagine a user downloads:
logo.png
Do they really need to download the exact same logo on every page visit?
Often, no.
You might configure caching headers for static files:
location ~* \.(jpg|jpeg|png|gif|css|js|svg|woff2)$ {
expires 30d;
}
This can encourage browsers to reuse cached static assets.
But be careful.
Long caching becomes problematic if you replace:
app.js
while browsers continue using an old copy.
That's why production systems often use versioned or hashed filenames:
app.83da81.js
When the content changes, the filename changes.
Browser caching happens on the visitor's device.
NGINX itself can also cache responses from upstream servers.
Conceptually:
First Request
|
v
NGINX
|
Cache Miss
|
v
Backend Application
|
v
Store Response
Next Request
|
v
NGINX
|
Cache Hit
|
v
Return Cached Response
NGINX supports proxy caching with directives such as proxy_cache_path, proxy_cache, and proxy_cache_valid.
A simplified configuration might resemble:
proxy_cache_path /var/cache/nginx
keys_zone=my_cache:10m;
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
}
}
Caching is powerful.
Incorrect caching is also a great way to create serious bugs.
You probably don't want to blindly cache:
User dashboards
Shopping carts
Authenticated pages
Account information
Personalized responses
Checkout pages
Admin panels
Imagine User A opens:
/account
and the response gets cached.
Then User B receives User A's account page.
That's obviously unacceptable.
Pro Tip: Cache only when you understand whether the response is public, private, personalized, authenticated, or state-dependent.
When NGINX communicates with backend services, different stages can have timeouts.
For example:
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
These control different parts of communication with an upstream application.
Don't randomly change every timeout to:
600 seconds
because your application sometimes hangs.
That may simply hide a slow backend problem.
Ask instead:
Why is this request taking so long?
When NGINX proxies an application response, it can buffer data before delivering it to the client.
This can be useful because your backend application doesn't necessarily need to remain tied to a slow client connection for the entire delivery.
However, some applications—especially streaming or real-time responses—may require different buffering behavior.
That's why understanding this distinction becomes important when working with:
This is an advanced topic.
Don't disable buffering just because a random tutorial tells you to.
Have you ever tried uploading a file through NGINX and received:
413 Request Entity Too Large
One configuration you may need is:
client_max_body_size 20M;
For example:
server {
client_max_body_size 20M;
}
But remember that your application may also have its own upload limit.
For PHP, for example, you may also need to consider settings such as:
upload_max_filesize
post_max_size
So a successful upload can involve multiple layers:
Browser
|
v
NGINX upload limit
|
v
PHP upload limit
|
v
Application validation
Imagine your project contains:
.env
.git/
config/
backup.sql
You probably don't want visitors downloading them.
Your web root should point only to files intended to be publicly accessible.
This is especially important for frameworks.
For example, many PHP frameworks use a structure like:
project/
├── app/
├── config/
├── storage/
├── vendor/
└── public/
Your NGINX root should normally point to:
project/public/
not:
project/
That architectural decision is often more valuable than trying to write complicated NGINX rules to protect every private file afterward.
root and aliasThis becomes important as configurations grow.
Consider:
location /images/ {
root /var/www;
}
A request:
/images/logo.png
is mapped using the request URI with the configured root.
Now compare:
location /images/ {
alias /data/pictures/;
}
alias replaces the matched location portion with another filesystem path.
This is one of those areas where a missing slash or misunderstanding of path construction can lead to confusing 404 errors.
Pro Tip: When debugging static-file problems, determine the exact filesystem path NGINX is trying to open.
Don't stare only at the URL.
Suppose you have:
example.com
api.example.com
admin.example.com
You could configure separate server blocks:
server {
listen 80;
server_name example.com;
root /var/www/frontend;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:4000;
}
}
Now one NGINX installation routes traffic to multiple applications.
That is the point where NGINX starts feeling less like "a web server" and more like a traffic controller.
Here's an analogy that makes many advanced concepts easier.
Imagine a large office building.
NGINX is the receptionist at the front desk.
Someone walks in and says:
I'm here for example.com
NGINX checks:
server_name
Then the visitor says:
I need /api/users
NGINX checks:
location
Then it decides:
That's handled by application server 2.
Another person asks:
I need /images/logo.png
NGINX says:
I already have that file.
Here you go.
Another arrives using insecure HTTP.
NGINX says:
Please use HTTPS instead.
Another sends 500 requests per second.
NGINX says:
Too many requests.
Once you see NGINX this way, the configuration becomes much more logical.
By now, this configuration should look much less mysterious:
upstream app_backend {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://app_backend;
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;
}
}
Read it in plain English:
Create a backend called app_backend.
It contains an application running at:
127.0.0.1:3000.
Listen for HTTP traffic on port 80.
Handle requests for:
example.com
www.example.com
For requests beginning with /,
send them to app_backend.
Also forward useful request information.
That's all.
NGINX configurations become dramatically easier when you translate them into plain language.
Here's another example:
server {
listen 80;
server_name example.com;
root /var/www/example/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
}
location ~* \.(jpg|jpeg|png|gif|css|js|svg)$ {
expires 30d;
}
}
You can now mentally separate the responsibilities.
NGINX serves it.
try_files eventually sends it to index.php.
NGINX sends it to PHP-FPM.
That's the architecture.
Don't try to learn NGINX randomly.
I recommend this order:
Level 1
What is NGINX?
|
v
server + listen + server_name
|
v
root + index
|
v
location
Then:
Level 2
try_files
|
v
reverse proxy
|
v
proxy headers
|
v
PHP-FPM / FastCGI
|
v
logs + troubleshooting
Then:
Level 3
HTTPS
|
v
upstreams
|
v
load balancing
|
v
caching
|
v
rate limiting
|
v
timeouts + buffering
Then:
Level 4
performance tuning
|
v
security hardening
|
v
high availability
|
v
observability
|
v
production architecture
Learning in this order is much easier than opening a 300-line production configuration and trying to understand everything at once.
This is probably the biggest one.
You copy:
proxy_buffering off;
Why?
No idea.
Someone on Stack Overflow had it.
Avoid this habit.
Every time you add a directive, ask:
What problem is this solving?
nginx -tAlways test configuration first.
sudo nginx -t
Then reload.
This simple habit can save you a lot of unnecessary debugging.
NGINX might receive the request.
But your application still needs to run somewhere.
For example:
NGINX
|
+--> PHP-FPM
or:
NGINX
|
+--> Node.js
or:
NGINX
|
+--> Python
NGINX doesn't automatically become your PHP, Node.js, or Python runtime.
If NGINX returns an error, logs should be one of the first places you check.
Don't make configuration changes blindly.
Restarting sometimes makes a symptom disappear.
It doesn't necessarily explain the problem.
Check:
sudo nginx -t
Check the service status.
Check the logs.
Check the upstream application.
Then fix the actual issue.
Imagine:
NGINX = 80 / 443
Node.js = 3000
PHP-FPM = Unix socket
Database = 3306
Redis = 6379
These services have different jobs.
You don't expose every application port publicly just because it exists.
Often NGINX should be the public entry point.
This:
location /api/ {
}
is easy to understand.
A giant regular expression may technically work, but unnecessary complexity makes future debugging harder.
Start simple.
You may find tutorials recommending:
Huge worker limits
Extreme caching
Maximum gzip compression
Large buffers
Long timeouts
without knowing anything about your server.
That's not optimization.
That's guessing.
Measure first.
Tune second.
Check the installed version:
nginx -v
See additional build information:
nginx -V
Test configuration:
sudo nginx -t
Reload:
sudo systemctl reload nginx
Restart:
sudo systemctl restart nginx
Check service status:
sudo systemctl status nginx
NGINX itself also supports signals including stop, quit, reload, and reopen, while -t tests configuration before it is applied.
If this entire article feels like too much, start with only these five.
serverWhich website should handle the request?
listenWhich port receives the request?
server_nameWhich domain belongs to this configuration?
locationWhich rule should handle this URL?
proxy_pass or fastcgi_passWhere should dynamic requests go?
With only these concepts, you can already understand a huge number of real NGINX configurations.
Being advanced with NGINX doesn't mean memorizing every directive.
It means looking at a request and understanding its journey.
For example:
https://api.example.com/users?page=2
An experienced developer thinks:
1. DNS points api.example.com to the server.
2. Connection reaches NGINX on port 443.
3. TLS is handled.
4. NGINX selects the server block for api.example.com.
5. NGINX evaluates the URI against location blocks.
6. /users matches the application's route.
7. NGINX proxies the request to an upstream.
8. Headers are forwarded correctly.
9. The backend processes the request.
10. The response returns through NGINX.
11. NGINX may buffer, compress, cache, log, or otherwise
process the response.
12. The browser receives it.
That mental model is far more useful than memorizing configuration snippets.
After learning these concepts, a production system may no longer look mysterious.
It could look like:
Internet
|
v
HTTPS
|
v
NGINX
|
+---------------+---------------+
| | |
v v v
Static Files API Server PHP-FPM
|
+------+------+
| |
v v
App Node 1 App Node 2
|
v
Database
NGINX can sit at the center handling:
TLS termination
Domain routing
URL routing
Static content
Reverse proxying
FastCGI
Load balancing
Rate limiting
Caching
Compression
Logging
That's why learning it properly is worth the effort.
NGINX looked complicated to me mainly because individual configuration snippets don't explain the bigger picture.
Once you understand that NGINX is essentially deciding:
Who is this request for?
Which URL was requested?
Where should it go?
How should it be handled?
everything starts connecting.
Don't begin by trying to memorize hundreds of directives.
Start with:
server
listen
server_name
location
root
try_files
proxy_pass
fastcgi_pass
Then learn:
HTTPS
upstreams
load balancing
logs
caching
rate limiting
timeouts
Eventually, you'll be able to open a production NGINX configuration and understand not only what it does but why each section exists.
If you're learning NGINX right now, create one small local server configuration and experiment with different location blocks.
Change one thing.
Run:
sudo nginx -t
Reload it.
Make a request.
Watch what happens.
That's one of the fastest ways to turn NGINX from mysterious configuration syntax into something you genuinely understand.
Which NGINX concept confused you the most when you first started learning it: location, reverse proxying, PHP-FPM, SSL, or something else?
It can be both.
NGINX can serve files directly as a web server, or it can sit in front of applications and forward requests to them as a reverse proxy. It can also perform load balancing, caching, TLS termination, and other traffic-management tasks.
A server block defines how NGINX should handle requests for a particular address, port, and hostname.
For example:
server {
listen 80;
server_name example.com;
}
NGINX uses the listening address/port and server-name matching rules to choose the appropriate virtual server.
location / mean in NGINX?A location block defines configuration based on the requested URI.
The simple:
location / {
}
acts as a broad prefix match and can handle requests when a more specific location doesn't win.
NGINX supports prefix, exact, and regular-expression location matching.
proxy_pass and fastcgi_pass?proxy_pass is commonly used when forwarding HTTP requests to another HTTP application server.
For example:
proxy_pass http://127.0.0.1:3000;
fastcgi_pass communicates with a FastCGI server such as PHP-FPM:
fastcgi_pass unix:/run/php/php-fpm.sock;
NGINX's official beginner guide documents both proxying and FastCGI as separate mechanisms.
A common reason is that NGINX cannot successfully communicate with the configured upstream application.
Check:
The exact cause depends on your setup, so checking the logs is more useful than guessing.
Usually you can test the configuration and reload it instead.
sudo nginx -t
sudo systemctl reload nginx
NGINX supports graceful configuration reloads, allowing new workers to use the new configuration while old workers finish existing requests.
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google
Kritim Yantra
Kritim Yantra
Kritim Yantra