Last updated: @2017-04-16
At the very moment of starting Nginx, one unique process exists in memory: the Master Process.
It is launched with the current user and group permissions (usually root/root) if the service is launched at boot time by an init script.
The master process itself does not process any client request, instead, it spawns processes that do the Worker Processes, which are affected to a customizable user and group.
From the configuration file, you are able to define the amount of worker processes, the maximum connections per worker process, the user and group the worker processes are running under, and more.
Nginx makes no difference whether you use single or double quotes.
Ex.:
root '/home/example.com/my web pages';
or
root "/home/example.com/my web pages";
Nginx's directives are case sensitive => Server_name != server_name
Nginx modules also provide variables that can be used in the definition of directive values.
Ex.: the Nginx HTTP Core module defines the $nginx_version variable.
log_format main '$pid - $nginx_version - $remote_addr';
Note: variables inserted in strings within quotes will be expanded normally, unless you prefix the $ character with a backslash ( \ ).
An Nginx server block defines the routes and rules applied for a specific domain served by Nginx.
If you are experienced with Apache, a server block is the equivalent of an Apache vhost.
This command returns the list of modules enabled at compile time for nginx:
nginx -V
This command parses nginx's config files and checks for syntax errors
nginx -t
Reference: how to add the gzip module to nginx
Nginx comes with gzip compression already enabled but just for html files. Binary files should not be compressed but other text files like .css, .js, .json etc. should be.
To extend nginx's gzip functionalities edit /etc/nginx/nginx.conf
##
# `gzip` Settings
#
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
# this skips compression for very small files
gzip_min_length 256;
# this defines the MIME types for which compression should be enabled
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
To apply
service nginx restart
Compression can be tested with the following command:
curl -H "Accept-Encoding: gzip" -I http://localhost/test.html
In response, you should see several HTTP response headers:
Nginx response headers
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 19 Jan 2016 20:04:12 GMT
Content-Type: text/html
Last-Modified: Tue, 04 Mar 2014 11:46:45 GMT
Connection: keep-alive
Content-Encoding: gzip
The last line proves that the file has been served compressed.
You can test any other kind of file at the same way.
curl -H "Accept-Encoding: gzip" -I http://localhost/test.css
To enable SSL encryption in a webserver the first step is to create valid certificates for the domain.
The HTTPS method relies on some certificates released by a Certification Authority (CA). The CA needs to receive a CSR to create valid certificates.
All versions of nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH). Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults, which includes a 1024-bit key for the key-exchange.
Since Letsencrypt and Namecheap are using a 2048-bit certificate, nginx will use a weaker key-exchange therefore it's better to generate a stronger DHE parameter to further increase security:
Use this command:
openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Now create this file /etc/nginx/snippets/ssl-params.conf
and add this content:
# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now. You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
# add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
If you have your own DNS server you might want to replace this line
resolver 8.8.8.8 8.8.4.4 valid=300s;
with
resolver your_dns_server_ip 8.8.8.8 valid=300s;
dhparam.pem
will used later on. Move on.
In the following examples I will consider a real case with Namecheap.com CA
The first step happens on the host side with the production of certificates uniquely identifying the domain.
A CSR is a file containing your certificate application information, including your Public Key.
To generate a pair of private key and public Certificate Signing Request (CSR) for a webserver use :
cd /root
openssl req -nodes -sha256 -newkey rsa:2048 -keyout example--com_ssl.key -out example--com_ssl.csr
This command creates a two files in the current folder:
- example--com_ssl.key which contains a private key; do not disclose this file to anyone and back it up because there is no means to recover it should it be lost.
- example--com_ssl.csr which contains the Certificate Signing Request
The private key is used as input in the command to generate the Certificate Signing Request (CSR).
After running the command you will be asked to enter details to be entered into your CSR.
For some fields there will a default value and if you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]: GB
State or Province Name (full name) [Some-State]: Yorks
Locality Name (eg, city) []: York
Organization Name (eg, company) [Internet Widgits Pty Ltd]: MyCompany Ltd
Organizational Unit Name (eg, section) []: IT
Common Name (eg, YOUR name) []: **mysubdomain.example.com**
Email Address []:
A challenge password []:
An optional company name []:
Use a fully qualified domain name (FQDN) as Common Name (CN)
For a webserver certificate these fields can be left blank:
- email address
- optional company name
- challenge password
You can check the CSR signature with this command:
openssl req -in example--com_ssl.csr -text -noout
Everything is ready to start the "PositiveSSL" certificate to Namecheap and the full content of the example--com_ssl.csr file will be requested during the registration process through the web interface
Login to Namecheap/Your SSL certificate, click "Activate". You will be asked to select the server type. Select Apache + OpenSSL (even though you are using Nginx). You then are asked to paste the certificate signing request (CSR). On your server, do a
cat example--com_ssl.csr
and copy and paste the everything (including "----BEGIN..." and "----END...") to the Namecheap web page's text-area and click submit. You will then be asked enter additional information, enter them.
You will receive a email from Comodo asking you to open a link and paste some code that's provided in the mail. Do that.
After some time, you will get your certificate in the email. Download the zip file.
It contains 4 files:
- example--com.crt
- COMODORSADomainValidationSecureServerCA.crt
- COMODORSAAddTrustCA.crt
- AddTrustExternalCARoot.crt
Now you need to combine most of the Comodo crt files into a single site crt file Nginx will use. The AddTrustExternalCARoot.crt file is not needed because the client's browser, like Firefox or Chrome, already has a list of root certs they will trust. When combining the crt files the order is important to Nginx:
cat example--com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt > example--com_ssl.crt
You can check the signature of the combined file with this command:
openssl x509 -noout -text -in example--com_ssl.crt
Now move or copy there these 2 files
- example--com_ssl.crt
- example--com_ssl.key
to /etc/ssl/certs
:
tree -d /etc/ssl/
Output:
/etc/ssl/
├── certs
└── private
And add fill /etc/nginx/snippets/ssl-example--com.conf
with this content:
echo "ssl_certificate /etc/ssl/certs/example--com_ssl.crt;" > /etc/nginx/snippets/ssl-example--com.conf
echo "ssl_certificate_key /etc/ssl/certs/example--com_ssl.key;" >> /etc/nginx/snippets/ssl-example--com.conf
Letsencrypt is a free, automated and open Certificate Authority. This is enough to understand how awesome it is.
From the Nginx perspective, there is no difference between Namecheap and Letsencrypt certificates but from the configuration point of view there are differences, so I prefer to write all the steps separately and indipendently from those exposed above (i.e. you don't need to read them, you can just focus on this paragraph).
I took most of the instructions from this awesome article but I've adjusted it accordingly to my experience.
Letsencrypt's certfication is automated: what does it mean? A few things:
- the creation and installation of certificates happens through a specific tool called
certbot
- letsencrypt certificates expire after 3 months but
certbot
can automatically renew them until necessary
If you don't have Debian's backports repository configured, add it:
echo 'deb http://ftp.debian.org/debian jessie-backports main' | sudo tee /etc/apt/sources.list.d/backports.list
Then install certbot
apt-get install certbot -t jessie-backports
The certification process, in a nutshell, works like this:
- certbot will contact the CA requesting a new certificate for a specific domain
- the CA will try to reach the specific domain to verify that the domain exists and will look for a specific path to ensure that the request is valid
- once the verified the CA will send back to certbot the certificate and it will save it on the harddrive
- the obtained certificate must be included in the server block
- the system must configured to renew the certificate every 90 days
Therefore the first thing to do is to configure the nginx server block in order to provide access to the specif path which is $webroot/.well-known/acme-challenge
Assuming that /var/www/example--com
is the domain's webroot
cd /var/www/example--com
If the webroot is /var/www/example--com
:
cd /var/www/example--com/public
Create the folder:
mkdir -p .well-known/acme-challenge
chown www-data:www-data -fR .well-known
Configure the server block in the nginx domain config file:
cd /etc/nginx/sites-enabled
vim example--com.conf
Add this information block
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
Reload nginx
service nginx reload
Test it:
curl http://example.com/.well-known/acme-challenge/
You should get a 403 and an output like this one:
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
There are several plugins for certbot which are capable to automate a lot the certificate installation but I prefer to use the simple webroot
plugin which just triggers the domain validation and the download of the certificate. I will then configure nginx manually.
This command will request a new certificate to Letsencrypt CA for the domain example.com and www.example.com. As far as I understand letsencrypt does not provide wildcard certificates (like *.example.com) therefore you need to specify all the subdomains you want to certify in the command line. Letsencrypt does support though any subdomain level (like something.www.example.com)
certbot certonly --webroot -w /var/www/example--com/public/ -d example.com -d www.example.com
If you are running certbot for the first time on the system, it will ask you to provide an email address that will be used for notifications and in case of recovery.
If everything goes well this will be the output
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
http-01 challenge for www.example.com
Using the webroot path /var/www/example--com/public for all
unmatched domains.
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits):
/etc/letsencrypt/keys/0002_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0002_csr-certbot.pem
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/example.com/fullchain.pem. Your cert will
expire on 2017-07-15. To obtain a new or tweaked version of this
certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run "certbot
renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Yes, please, consider donating to this awesome project.
Be aware that after a few failed attempts in a row, letsencrypt will block the certification of such a domain for a limited amount of time (maybe an hour, something like that)
You can now see the certificates for example.com in your filesystem:
tree ls -l /etc/letsencrypt/live/example.com/
ls [error opening dir]
/etc/letsencrypt/live/example.com/
├── cert.pem -> ../../archive/example.com/cert1.pem
├── chain.pem -> ../../archive/example.com/chain1.pem
├── fullchain.pem -> ../../archive/example.com/fullchain1.pem
└── privkey.pem -> ../../archive/example.com/privkey1.pem
Now add fill /etc/nginx/snippets/ssl-example--com.conf
with this content:
echo "ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;" > /etc/nginx/snippets/ssl-example--com.conf
echo "ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;" >> /etc/nginx/snippets/ssl-example--com.conf
Edit /etc/crontab
and add this line
17 3 * * 1 root certbot renew >> /var/log/le-renew.log
Every Monday at 3:17 am your system will check if certificates require renewal and, in that case, will contact letsencrypt for automatic renewal. Be aware that, if the renewal doesn't happen in the next 90 days from the creation time your certificates will expire and you will have to recreate them again. The output of the command will be logged in /var/log/le-renew.log
It's all set for configuring nginx to use our certificates, doesn't matter if we are using Namecheap's or Letsencrypt's ones.
Before starting you need to understand which strategy you want nginx to perform:
- strategy 1: to answer only to port 443 and completely drop the support for non-encrypted traffic. This means that any request like http://example.com will produce a 404 error
- strategy 2to answer on port 80 and redirect all the traffic on port 443 enforcing encryption. This means that any request like http://example.com will be redirected to https://example.com
- strategy 3support both encrypted and not encrypted communication letting the client chose the kind of connection. This means that any request like http://example.com will be answered unencrypted while any connection established with https will be encrypted.
strategy 1
The nginx server block inside the /etc/nginx/sites-available/example--com.conf
file will look like this one:
server {
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example--com/public;
ssl on;
include /etc/nginx/snippets/ssl-example--com.conf;
include /etc/nginx/snippets/ssl-params.conf;
# ...
}
strategy 2
The nginx server block inside the /etc/nginx/sites-available/example--com.conf
file will look like this one:
server { listen 80; server_name example.com www.example.com ; #redirects all the traffic on https return 301 https://$server_name$request_uri; }
server { # SSL configuration
listen 111.222.1.111:443 ssl;
#server_name example.com www.example.com ;
include snippets/ssl-malishor--com.conf;
include snippets/ssl-params.conf;
# ...
}
strategy 3
The nginx server block inside the /etc/nginx/sites-available/example--com.conf
file will look like this one:
server {
listen 80;
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example--com/public;
ssl on;
include /etc/nginx/snippets/ssl-example--com.conf;
include /etc/nginx/snippets/ssl-params.conf;
# ...
}
and enable it by linking it in the sites-enabled folder
cd /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/example--com.conf
Test if the system is able to resolv the domain:
ping example.com
If the domain is unreachable or it points to a wrong ip address, edit the /etc/hosts
file or change your dns entries accordingly
Then restart nginx.
nginx -t && service nginx restart
To test if SSL is working correctly, run telnet from your client computer (preferably)
telnet example.com 443
When the connection is established type:
GET / HTTP/1.0
and press Enter twice
This would be the output for a non encrypted request (strategy 3)
telnet example.com 80
Trying 111.222.1.111...
Connected to example.com.
Escape character is '^]'.
GET / HTTP/1.0
double enter here
Output:
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 16 Apr 2017 14:50:07 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 11
Last-Modified: Sun, 16 Apr 2017 12:51:10 GMT
Connection: close
ETag: "58f368be-b"
Accept-Ranges: bytes
<html>
<head><title>Example.com home page</title></head>
<body bgcolor="white">
<p>this the content of index.html at example.com</p>
</body>
</html>
Connection closed by foreign host.
This would be the output for an encrypted request (strategy 2)
telnet example.com 443
Trying 111.222.1.111...
Connected to example.com.
Escape character is '^]'.
GET / HTTP/1.0
double enter here
Output:
HTTP/1.1 400 Bad Request
Server: nginx
Date: Sun, 16 Apr 2017 14:53:43 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 264
Connection: close
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx</center>
</body>
</html>
Connection closed by foreign host.
For additional security you might want to disguise your webserver signature to confuse a potential attacker.
One comfortable way for changing nginx's signature is to install the extras package
apt-get install nginx-extras
and add these two lines to /etc/nginx/nginx.conf
http{
more_set_headers "Server: Apache/2.4.10 (Ubuntu)";
server_tokens off;
}
Restart nginx
nginx -t && service nginx restart
Test it
telnet example.com 443
Trying 111.222.1.111...
Connected to example.com.
Escape character is '^]'.
GET / HTTP/1.0
HTTP/1.1 400 Bad Request
Date: Sun, 16 Apr 2017 15:10:26 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 264
Connection: close
**Server: Apache/2.4.10 (Ubuntu)**
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx</center>
</body>
</html>
Connection closed by foreign host.
If you look carefully, the signature is changed but the content of the 400 error page still shows nginx
Be aware of that and customize Nginx's error pages accordingly. See this for more information.