Enabling HTTP/2 on Nginx

The HTTP/2 protocol is the latest craze with web servers at the moment. The updated protocol has many performance enhancements over the older HTTP/1.1 protocol mainly due to the fact requests are downloaded in parallel, so therefore its multiplexed over a single connection.

While HTTP/2 will work with non-ssl enabled websites, popular browsers such as Firefox and Chrome will only support HTTP/2 for SSL enabled websites. So how do we get started with HTTP/2 on Nginx?

Nginx makes HTTP/2 simple! The HTTP/2 protocol has been included in Nginx since version 1.9.5, so it works in CentOS 6 and 7 as well as Ubuntu 14.04, 16.04 and 18.04.

A special note about Ubuntu 14.04 which is set to go EOL on 4/2019, the default Nginx package that gets installed via apt is very old (v1.4.6) and therefore does not support HTTP/2. However if you install Nginx from the ppa:nginx/stable PPA, you can make use of HTTP/2. You can install nginx from the PPA by:

# This applies to Ubuntu 14.04 ONLY.
[root@web01-ubuntu1404 ~]# apt-get install python-software-properties
[root@web01-ubuntu1404 ~]# add-apt-repository ppa:nginx/stable
[root@web01-ubuntu1404 ~]# apt-get update
[root@web01-ubuntu1404 ~]# apt-get install nginx

For CentOS 6 and 7, you can install Nginx from EPEL. On Ubuntu 16.04 and 18.04, HTTP/2 will work fine with the default Nginx packages provided by Ubuntu.

From here, enabling HTTP/2 is as easy as updating the listen directive within the Nginx vhost config to include ‘http2’ as shown below:

[root@web01 ~]# vim /etc/nginx/conf.d/example.com.conf
...
listen 443 ssl http2

Now check the Nginx configuration to ensure there are no problems:

[root@web01 ~]# nginx -t

Finally, restart Nginx to apply the change:

[root@web01 ~]# service nginx restart

You can test to ensure its working by checking for the protocol header as shown below:

[root@workstation ~]# curl -IL https://www.example.com
...
HTTP/2 200
...

Nginx Load Balancer

Load balancing applications across multiple servers is a common technique for creating highly available, fault-tolerant solutions. Nginx can serve as a very light weight and efficient HTTP load balancer to distribute traffic to multiple servers.

This guide will outline one basic way Nginx can be deployed as a load balancer.

Install Nginx

First, install Nginx on your server. In many cases, the version of Nginx supplied by the OS repos is outdated, so this guide will be using Nginx’s official repos instead.

# CentOS 6
[root@lb01 ~]# rpm -i http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
[root@lb01 ~]# rpm --import http://nginx.org/keys/nginx_signing.key
[root@lb01 ~]# yum install nginx
[root@lb01 ~]# chkconfig nginx on

# CentOS 7
[root@lb01 ~]# rpm -i http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
[root@lb01 ~]# rpm --import http://nginx.org/keys/nginx_signing.key
[root@lb01 ~]# yum install nginx
[root@lb01 ~]# systemctl enable nginx

# Ubuntu 14.04 and 16.04
[root@lb01 ~]# nginx=stable # use nginx=development for latest development version
[root@lb01 ~]# apt-get install python-software-properties
[root@lb01 ~]# add-apt-repository ppa:nginx/$nginx
[root@lb01 ~]# apt-get update
[root@lb01 ~]# apt-get install nginx

Create Load Balancer config

As I have many servers running different domains, I will be setting up Nginx to load balance specific domains to specific servers. So in the example below, www.example.com will be load balanced to my 2 servers, 192.168.1.161 and 192.168.1.162.

The notable things defined in this example include:

- Uses Least Connections for routing traffic to the load balanced web servers
- Servers will automatically be disabled in the load balancer
- The load balancer will pass the x-forwarded-for header (real IP) to the load balanced web servers

Create the config by:

[root@lb01 ~]# vim /etc/nginx/conf.d/example.com.conf
upstream lb-example.com {

	# Load balancing method, only enable one.
	# blank        # Round robin, leave commented out as it is the default
        least_conn;    # Least connections
	# ip_hash;     # Sticky sessions 

	# Backend load balanced servers
        server 192.168.1.161 max_fails=3 fail_timeout=15s;
        server 192.168.1.162 max_fails=3 fail_timeout=15s;

	# Temporarily disable server in load balancer
        # server 192.168.1.163 down;
}

server {
        listen 80;
        server_name www.example.com example.com;
        access_log  /var/log/nginx/example.com.access.log  main;
        # Enforce https
        # return 301 https://$server_name$request_uri;
        # }

        # Pass to load balanced servers
        location / {
        proxy_pass http://lb-example.com;
	
	# Pass x-forwarded-for headers to backend servers
	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;
        }
}

server {
        listen 443 ssl;
        server_name www.example.com example.com;
        access_log  /var/log/nginx/example.com.access.log  main;

        location / {
        proxy_pass https://lb-example.com;

        # Pass x-forwarded-for headers to backend servers
        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;
        }
}

Now test the new configuration to ensure there are no errors and restart nginx:

[root@lb01 ~]# nginx -t
[root@lb01 ~]# service nginx restart

Ensure that you open the software firewall for 80 and 443 accordingly. Below is an example for CentOS 6:

[root@lb01 ~]# vim /etc/sysconfig/iptables
...
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
...
[root@lb01 ~]# service iptables restart

How to install Nginx and PHP-FPM on CentOS

Nginx is a very popular web server in recent years. It is very well known for being used as a high performance web server, a reverse proxy server, or even a load balancer. Some differences Nginx has from Apache:

- No built in PHP support (mod_php).
- Apache is process based while Nginx is event based.
- Modules in Nginx are compiled directly into the binary while Apache has installable modules that can be enabled or disabled quickly
- Nginx configuration files are a series of includes for each sites configuration, whereas Apache has global settings and a companion virtual host with overrides for customization.  So this means there is no override file like Apache's .htaccess in Nginx.
- There are no SSLCertificateChainFile in Nginx.  Intermediate CA certificates need to be appended directly to the site's SSL certificate.

At this time, the current best practice for installing Nginx is to use Nginx’s repos directly as they will contain the most recent and stable releases of Nginx. So install the repo package for Nginx by:

# CentOS 6
[root@web01 ~]# rpm -i http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
[root@web01 ~]# rpm --import http://nginx.org/keys/nginx_signing.key
[root@web01 ~]# yum install nginx

# CentOS 7
[root@web01 ~]# rpm -i http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
[root@web01 ~]# rpm --import http://nginx.org/keys/nginx_signing.key
[root@web01 ~]# yum install nginx

As Nginx does not have anything to natively process PHP, install php5-fpm by:

[root@web01 ~]# yum install php-fpm

By default, php-fpm will be listening for connections over TCP. This is incredibly slow when Nginx and php-fpm are on the same server. So modify the php-fpm configuration from listening over TCP to using sockets by:

[root@web01 ~]# vim /etc/php-fpm.d/www.conf
...
[www]
listen = /var/run/php5-fpm.sock
...

Now set php-fpm to run as user nginx:

[root@web01 ~]# vim /etc/php-fpm.d/www.conf
...
user = nginx
group = nginx
listen.owner = nginx
listen.group = nginx
...

Set php-fpm to start on boot, and then startup the service:

# CentOS 6
[root@web01 ~]# chkconfig php-fpm on
[root@web01 ~]# service php-fpm start

# CentOS 7
[root@web01 ~]# systemctl enable php-fpm.service
[root@web01 ~]# systemctl start php-fpm.service

Back in Nginx, configure it to send any PHP requests to the php-fpm socket inside of the http{} block:

[root@web01 ~]# vim /etc/nginx/nginx.conf
http {
...
upstream php5-fpm-sock {
     server unix:/var/run/php5-fpm.sock;
}
...
}

Now that Nginx and php-fpm are configured, setup your first site:

[root@web01 ~]# mkdir -p /var/www/vhosts/example.com
[root@web01 ~]# vim /etc/nginx/conf.d/example.com.conf
server {
     listen 80;
     server_name example.com www.example.com;

     root /var/www/vhosts/example.com;
     index index.php index.html index.htm;

     access_log /var/log/nginx/example.com-access.log;
     error_log /var/log/nginx/example.com-error.log;

     location ~ \.php$ {
          expires off;
          try_files $uri =404;
          include /etc/nginx/fastcgi_params;
          fastcgi_pass php5-fpm-sock;
          fastcgi_index index.php;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     }

     # caching of files
     location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires max;
          log_not_found off;
     }
}

#server {
#      listen 443 ssl;
#      server_name example.com www.example.com;
#
#      root /var/www/vhosts/example.com;
#      index index.php index.html index.htm;
#
#      access_log /var/log/nginx/example.com-ssl-access.log;
#      error_log /var/log/nginx/example.com-ssl-error.log;
#
#      ssl_certificate /etc/pki/tls/certs/YYYY_example.com.crt;
#      ssl_certificate_key /etc/pki/tls/private/YYYY_example.com.key;
#
#      ssl_session_timeout 5m;
#
#      # Use PCI compliant SSL protocols and ciphers
#      ssl_protocols SSLv3 TLSv1;
#      ssl_ciphers HIGH:!kEDH:!ADH:!EXPORT56;
#      ssl_prefer_server_ciphers on;
#
#      location ~ \.php$ {
#           expires off;
#           try_files $uri =404;
#           include /etc/nginx/fastcgi_params;
#           fastcgi_pass php5-fpm-sock;
#           fastcgi_index index.php;
#           fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#      }
#
#      # caching of files
#      location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
#           expires max;
#           log_not_found off;
#      }
# }

Finally, start Nginx and enable it to start on boot:

# CentOS 6
[root@web01 ~]# chkconfig nginx on
[root@web01 ~]# service nginx start

# CentOS 7
[root@web01 ~]# systemctl enable nginx.service
[root@web01 ~]# systemctl start nginx.service

NOTE : There is no SSLCertificateChainFile in nginx. CA Certs need to be appended to the certificate for the domain.

Troubleshooting

If you see a page that says ‘The page you are looking for is currently unavailable’, you’ll find Nginx is having trouble getting data from php-fpm (while using a socket). Make sure that the socket you specify in the nginx.conf and the listen line in www.conf pool file for PHP-FPM match.

If you get a ‘File Not Found’ page only on php pages (when using a TCP port for php-fpm) you can check the nginx logs, and you’ll probably see an error indicating it couldn’t connect on the port. Its possible this could be happening if you need to move the root and index definitions out of a location block (if they are defined there). This is because the php section has it’s own location, the documents won’t try to be found using definitions in the domain ‘location /’ block.

How to install Nginx and PHP-FPM on Ubuntu

Nginx is a very popular web server in recent years. It is very well known for being used as a high performance web server, a reverse proxy server, or even a load balancer. Some differences Nginx has from Apache:

- No built in PHP support (mod_php).
- Apache is process based while Nginx is event based.
- Modules in Nginx are compiled directly into the binary while Apache has installable modules that can be enabled or disabled quickly
- Nginx configuration files are a series of includes for each sites configuration, whereas Apache has global settings and a companion virtual host with overrides for customization.  So this means there is no override file like Apache's .htaccess in Nginx.
- There are no SSLCertificateChainFile in Nginx.  Intermediate CA certificates need to be appended directly to the site's SSL certificate.

At this time, the current best practice for installing Nginx is to use Nginx’s repos directly as they will contain the most recent and stable releases of Nginx. So install the repo package for Nginx by:

# Ubuntu 12.04 and 14.04
[root@web01 ~]# nginx=stable # use nginx=development for latest development version
[root@web01 ~]# apt-get install python-software-properties
[root@web01 ~]# add-apt-repository ppa:nginx/$nginx
[root@web01 ~]# apt-get update
[root@web01 ~]# apt-get install nginx

As Nginx does not have anything to natively process PHP, install php5-fpm by:

# Ubuntu 12.04 and 14.04
[root@web01 ~]# apt-get update
[root@web01 ~]# apt-get install php5-fpm php5-cli

By default, php-fpm will be listening for connections over TCP. This is incredibly slow when Nginx and php-fpm are on the same server. So modify the php-fpm configuration from listening over TCP to using sockets by:

[root@web01 ~]# vim /etc/php5/fpm/pool.d/www.conf
...
[www]
listen = /var/run/php5-fpm.sock
...

Now set php-fpm to run as user nginx:

[root@web01 ~]# vim /etc/php5/fpm/pool.d/www.conf
...
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
...

Back in Nginx, configure it to send any PHP requests to the php-fpm socket inside of the http{} block:

[root@web01 ~]# vim /etc/nginx/nginx.conf
http {
...
upstream php5-fpm-sock {
     server unix:/var/run/php5-fpm.sock;
}
...
}

Now that Nginx and php-fpm are configured, setup your first site:

[root@web01 ~]# mkdir -p /var/www/vhosts/example.com
[root@web01 ~]# vim /etc/nginx/conf.d/example.com.conf
server {
     listen 80;
     server_name example.com www.example.com;

     root /var/www/vhosts/example.com;
     index index.php index.html index.htm;

     access_log /var/log/nginx/example.com-access.log;
     error_log /var/log/nginx/example.com-error.log;

     location ~ \.php$ {
          expires off;
          try_files $uri =404;
          include /etc/nginx/fastcgi_params;
          fastcgi_pass php5-fpm-sock;
          fastcgi_index index.php;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     }

     # caching of files
     location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires max;
          log_not_found off;
     }
}

#server {
#      listen 443 ssl;
#      server_name example.com www.example.com;
#
#      root /var/www/vhosts/example.com;
#      index index.php index.html index.htm;
#
#      access_log /var/log/nginx/example.com-ssl-access.log;
#      error_log /var/log/nginx/example.com-ssl-error.log;
#
#      ssl_certificate /etc/pki/tls/certs/YYYY_example.com.crt;
#      ssl_certificate_key /etc/pki/tls/private/YYYY_example.com.key;
#
#      ssl_session_timeout 5m;
#
#      # Use PCI compliant SSL protocols and ciphers
#      ssl_protocols SSLv3 TLSv1;
#      ssl_ciphers HIGH:!kEDH:!ADH:!EXPORT56;
#      ssl_prefer_server_ciphers on;
#
#      location ~ \.php$ {
#           expires off;
#           try_files $uri =404;
#           include /etc/nginx/fastcgi_params;
#           fastcgi_pass php5-fpm-sock;
#           fastcgi_index index.php;
#           fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#      }
#
#      # caching of files
#      location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
#           expires max;
#           log_not_found off;
#      }
# }

Finally, restart Nginx and PHP-FPM to apply the changes

# Ubuntu 12.04 and Ubuntu 14.04
[root@web01 ~]# service php5-fpm restart
[root@web01 ~]# service nginx restart

NOTE : There is no SSLCertificateChainFile in nginx. CA Certs need to be appended to the certificate for the domain.

Troubleshooting

If you see a page that says ‘The page you are looking for is currently unavailable’, you’ll find Nginx is having trouble getting data from php-fpm (while using a socket). Make sure that the socket you specify in the nginx.conf and the listen line in www.conf pool file for PHP-FPM match.

If you get a ‘File Not Found’ page only on php pages (when using a TCP port for php-fpm) you can check the nginx logs, and you’ll probably see an error indicating it couldn’t connect on the port. Its possible this could be happening if you need to move the root and index definitions out of a location block (if they are defined there). This is because the php section has it’s own location, the documents won’t try to be found using definitions in the domain ‘location /’ block.

Nginx stub_status module

Like the Apache mod_status module, Nginx also has a module that can provide you with realtime data regarding its stats. While its not as detailed as Apache mod_status, the data is still useful, especially if you are using Nginx as a load balancer.

First, confirm that Nginx was compiled with the HttpStudStatusModule. If you installed this via yum or apt-get, it should have it:

[root@web01 ~]# nginx -V 2>&1 | grep -o with-http_stub_status_module
with-http_stub_status_module

To enable the module, set the following configuration:

[root@web01 ~]# vim /etc/nginx/conf.d/nginx_status.conf
server {

    listen 80 default_server;
    access_log off;
    server_name _;
    server_name_in_redirect off;
    root  /var/www/html;

        location /nginx_status {
                # Enable Nginx stats
                stub_status on;
                # Disable logging for stats
                access_log   off;
                # Security: Only allow access from authorized IP's
                allow 127.0.0.1;
                allow 192.168.1.100;
                # Deny everyone else
                deny all;
        }

}

Now navigate your browser to:

http://serverip/nginx_status

The results will look something like this:

Active connections: 1 
server accepts handled requests
 1 1 1 
Reading: 0 Writing: 1 Waiting: 0

Here is what these numbers mean, taken directly from Nginx’s documentation:

# Active connections
The current number of active client connections including Waiting connections.

# accepts
The total number of accepted client connections.

# handled
The total number of handled connections. Generally, the parameter value is the same as accepts unless some resource limits have been reached (for example, the worker_connections limit).

# requests
The total number of client requests.

# Reading
The current number of connections where nginx is reading the request header.

# Writing
The current number of connections where nginx is writing the response back to the client.

# Waiting
The current number of idle client connections waiting for a request.