What is going on?
Let’s start with an example, suppose that Kevin is a Doctor (a really good one). Kevin is really popular, and by chance, he’s your brother.
Due to his excellent job, he has so many visitors. Every visitor must visit his office to be examined and diagnosed. He is so good that too many patients come in every day than everyone.
Imagine there are 500 patients, and every one of them tries to reach Kevin for their problems at the same time. What will happen? Well, obviously, Kevin can’t handle and manage all of them, and also can’t respond to anyone. So what will Kevin do?
First, he realizes that he can't visit all 500 patients per day. cause every visitor can at least take a 5min minutes, and he does not have that much time to visit all of them so he comes up with a solution.
If every visitor could just send him all of their symptoms precisely on a paper, he would just check those papers in a second and write a proper prescription for that patient. So he hired some good doctors as his secretary ("Come on😂"), and after that, each patient has to go to one of them and show them their symptoms on a paper and those secratary doctors are gonna validate and check that symptoms paper and if it was ok they will send that paper to the doctor and bring the doctor's prescription back to the patient.
But let’s say you feel pain in your hand. You don’t need to go to the secretaries because Kevin is your brother, and when he’s at home, you can ask him to visit you and write you a prescription.
So what was it all about? That was the explanation of nginx in the simplest way (what the hell? Are you kidding me?) No, I’m not. Let me explain: Kevin is your website (Aha!). When you are developing your website on your local machine, it is only you and your website; you don’t need anything to handle your request when you are trying to access your website (you can have Nginx on your local, but that is not necessary), exactly like the time when Kevin was home, and you could ask him anything.
But when you deploy your website, there gonna be too many requests to your website, so there has to be something that can handle those requests properly, like the secretary doctors.
Alright, Nginx is our secretary doctor. You just need to configure it to act as you wish, like what? Like tell nginx to listen to port 80 and 443 (entrance of Kevin's clinic), and if the request is asking for that page, serve that page, and if the request is not coming from https ( if the patient hasn't worn a medical mask and it's paper is dirty) don’t let it access the website (don't send the symptoms paper to Kevins office).
See? Exactly like the secretary. We define our rules, and it has to perform according to them.
Nginx
So let's provide a proper explanation for Nginx:
Nginx exists to handle web traffic efficiently. Traditional web servers were slow and resource-hungry when handling many simultaneous connections, especially under high load. They blocked on each request, which wasted memory and killed performance.
Nginx was designed to fix that.
It uses an event-driven, non-blocking architecture, which allows it to handle thousands of concurrent connections using very little memory. Instead of letting your backend application deal with slow clients, file serving, TLS, and connection management, Nginx sits in front and absorbs that complexity.
In practice, Nginx solves these problems:
Serves static files fast without hitting your backend
- Shields backend applications from direct internet traffic
- Handles SSL/TLS so your app doesn’t have to
- Proxies and routes requests to the right backend service
- Keeps your application focused on business logic, not HTTP plumbing
That’s why Nginx is almost always placed in front of backend applications, not instead of them.
So let’s install it:
sudo apt update
sudo apt install nginx
After installation. You can find Nginx files in this directory /etc/ngix .
There is an important file in that directory named nginx.conf that contains all of the main configuration of Nginx.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
Include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
The user directive tells nginx to use the www-data user to execute the command it needs to run.
worker_processes directive tells nginx how many processes are needed to process the requests.
The pid directive tells Nginx where to write the PID (Process ID) of the master process (What? Alright, Nginx runs with one master process and multiple worker processes.” Whyyyy? What are you talking about?” The master process ID is written to this file so that the specific commands that we are going to talk about work properly, like: nginx -s reload, nginx -t, and systemd commands that are related to the Nginx service).
And the include directive includes all the files that end with .conf in /etc/nginx/modules-enabled directory, so if you want to add a config file, you can create a file in that directory with the .conf extension.
events block
The worker_connections determine the maximum requests that a processor can process.
The multi_accept directive tells Nginx to process the requests simultaneously or not (I recommend you to uncomment this directive).
http block
Here is where magic actually happens, and your HTTP request here is getting served by the structure of this block. Some of the directives are important to us, so we're gonna review them. You can search the others online (most of them are obvious just by looking at their names).
server_token: specify if the Nginx version has to be considered in the response header or not (I recommend to set this off just to prevent potential attacks)
default_type: specify default mime types in responses.
gzip: tell Nginx to compress the sending requests using gzip or not ( I recommend to set this to on)
gzip_type: specify which type of files must be compressed by gzip. You just need to provide its mime_types.
And at the end, you can see these two lines:
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
They are going to import some configs from those directories exactly into the http block, so if you want to configure your Nginx for your website, you'd better go there and create a config file (“Where exactly?”). I’ll show you:
server block
Whenever you are trying to configure Nginx for your website, you need to define a server block (just like the http or events block that we’ve discussed), which's exactly where you tell Nginx what to do when a request comes in for this specific website. So, each hostname needs a server block. You need to put that server block inside the http block (at the end of the block ) in nginx.conf, or you can define the block in a file with an arbitrary name and use the include directive to import that into the http block.
If you want to know the best practice of how to structure your Nginx configurations, check this link.
But for now, go inside the http block and at the end, add a server block like this:
server {
}
However, we need to fill that in to tell nginx what you do whenever a request comes in. Let me fill it with real config, and then I’ll explain each line for you:
server {
listen 80;
listen [::]:80;
server_name example.com;
root /srv/example/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
listen 80;: tell nginx to listen to port 80 (IPv4), cause the HTTP requests hit that port.
listen [::]:80: it also says listen to port 80, but for IPv6
server_name: defines which hostname this server block handles. You can type your domain here, like:
server_name example.com www.example.com;
Alternatively, you can type your server’s IP address if you haven't purchased a domain yet.
root: defines the base directory where files are served from, so you have to create that directory and put your website code in that directory, (let say you have laravel project, so you know that your index.php in public directory needs to be served so you put your code in /srv/example/ and define the root like /srv/example/public where your index.php exists and waiting for your requests). for example after defining the root when the user asks for this /css/app.css nginx looks for /srv/example/public/css/app.css .
Let’s move on!
By using the add_header directive, you can add headers to your responses. I recommend you add these:
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
These two prevent your site from being embedded in iframes on other domains and stop the browser from guessing file types.
index index.php: when a directory is requested (/), by default, Nginx looks for index.html in that root directory that you provided earlier, and it does not recognize your index file. Hence, you have to specify it for Nginx using the index directive, so after that, Nginx knows whenever a request comes in (/), it will look for index.php in the root directory.
charsert utf-8: You’d better use it to tell the browsers the default character encoding of your server.
Location block
Well, wel,l well, here we are with the location block. That is actually really important.
Let me break it down for you:
location takes a regex as an argument. How? By providing it exactly in front of the location directive keyword, so when we write something like this:
location / {
root: /path/to/root/
}
For example, whenever a user hits the /about endpoint, Nginx tries to find and serve the /path/to/root/about and if it could find the directory, it will go to check if an index file is there based on the index files that we’ve provided by index keyword, but if it fails to find the index file it will respond with 403 (Forbidden) but if it fails to find the about directory it will return 404 (Not found). Ok! As you recall, we’ve defined the root earlier for the whole server block, not just for a specific location, so the nginx tries to access a index.php in that root as a response to the user request.
So location tries to match a path that we provided for it and do what we’ve decided for it (based on its block).
So this (/) means everything (every path).
Like in this example we have:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
It uses the built-in $uri variable. $uri is the normalized request path, not a file and not a URL. It doesn't include protocol, domain or even query string. So when the user types https://example.com/about?search=”hello” the uri is /about.
How does try_files really work?
It will work in this order:
1. Try $uri as a file
Let say we have this request
/css/app.css
Nginx check:
/srv/example/public/css/app.css
Nginx says: If it exist, then serve it and stop.
If it dont:
2. Try $uri/ as a directory
Let say we have this request
/about
Nginx checks:
/srv/example/public/about/
If it exists, then serve the index file inside (based on the index keyword) and stop.
If it doesn't:
3. Fallback to /index.php
If neither a file nor a directory exists, Nginx internally rewrites the request to:
/index.php?$query_string
This sends the request to PHP so your application router (Laravel, Symfony, etc.) can handle it.
So what will happen when a user requests for /about .
So first nginx tries to access it as a file ❌
it will fail cause we don't have about file in public directory.
Then tries to access it as a directory ❌
again fails cause we don't have /about directory in public directory.
So it will send the request to index.php with its query string, so how does nginx send this to the application router to handle the request for it?
Few lines after our first location block we have another location block:
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
It will handle all of the request that matches /index.php and /index.php/somthing
So nginx hits this endpoint and uses fastcgi_pass to send the request to PHP-FPM or any FastCGI interface that your app needs. PHP needs PHP-FPM. Cause Nginx can't understand PHP code or any other language (except HTML and CSS) and doesn't know how to process them so it leaves the process to the FastCGI interface to process the request for it.
Find your FastCGI interface for your project (programming language) and use it here.
There is another location block that says:
location ~ /\.(?!well-known).* {
deny all;
}
It simply says that if a request tries to access hidden files like: .git, .htaccess, .env (except .well-known, cause it's used for SSL verification) block them all.
Using deny all.
We also have two more location blocks in this code:
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
They tell Nginx if the request was exactly /favicon.ico and /robots.txt ( the = means only this exact path) Disable the logs for these files. And the reason behind it is that the browsers request it constantly, and we don't want to pollute the logs.
error_page 404 /index.php: means if Nginx faced any error, try to find the 404 file, and if you couldn't find it, send the request to index.php to handle 404 pages instead of Nginx.
SSL certificate
Most of the time, you need to enable your server to use an HTTPS connection. So how can we enable it?
very easy! First, you need to get ssl certificate and SSL certificate keys (you can use Cloudflare's. It's free!). After that, you simply put them in your server and use these directives in your server block to activate SSL:
ssl_certificate /path/to/cloudflare.pem;
ssl_certificate_key /path/to/cloudflare.key;
Don't forget to change the listen directive values cause https connections use port number 443:
listen 443 ssl http2;
listen [::]:443 ssl http2;
And you can also add another server block to redirect all of the HTTP requests to HTTPS, and then no one can access your website through HTTP:
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri;
}
Nginx commands
Now you know how to configure your Nginx.
These are useful commands that you may need:
starting Nginx:
sudo systemctl start nginx
Stopping Nginx:
sudo systemctl stop nginx
Enabling Nginx:
so that every time our machine gets rebooted, Nginx starts automatically
sudo systemctl enable nginx
Disabling Nginx:
sudo systemctl disable nginx
Restarting Nginx:
After every change in the config, we need to restart our Nginx so that the changes get applied:
sudo systemctl restart nginx
Alternatively, we can use nginx command, like:
sudo nginx -s reload
Yeah, that's it!
That was all you needed to know to use and set up your Nginx.
Just install Nginx and configure it the way you want, then start the engine and you are ready to go!
Top comments (0)