Nginx with Let's Encrypt configuration

In this article I show how easy and fast install and configure nginx. Also we will obtain free SSL public certificate for our domain by Let'sEncrypt service.

In the end we will make advanced nginx configuration to support for proxying request to backend (flask in my case) and providing balancing.

Overview

All configuration we will make on CentOS 6.7 with kernel 2.6.32-573.8.1.el6.x86_64. As a test domain in this article we will use example.com and backend-node-02.example.com for configure balancing. However you should change this domain names to yous.

We need configure nginx twice: for obtain SSL certificate and for backend config, because of my backend app on flask does not provide required http content for web site root.

Installation

Nginx Installation

I recomend to use latest nginx version from official repo because EPEL provide too old package version (without new shiny features;]). However all configuration in this article produced with nginx version 1.8.1-1.el6.

cat << EOF > /etc/yum.repo.d/nginx.repo  
[nginx]
name=nginx repo  
baseurl=http://nginx.org/packages/centos/6/$basearch/  
gpgcheck=0  
enabled=1  
EOF  
yum --disablerepo=* --enablerepo=nginx install nginx-1.8.1-1.el6.ngx.x86_64  

LetsEncrypt(LE) Installation

LetsEncrypt is free public CA service. It distributed via github so we need to install git first.

yum install epel-release  
yum install git  
cd /opt  
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt  

Configuration

Base LE and nginx configuration. Obtaining certificates.

In the first place we should edit nginx.conf.

cat << EOF > /etc/nginx/nginx.conf  
user nginx;  
worker_processes 1; # ~ number of CPU  
error_log /var/log/nginx/error.log warn;  
pid /var/run/nginx.pid;  
worker_rlimit_nofile 2048;

events {  
    worker_connections 512;
}

http {  
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    limit_req_zone $binary_remote_addr zone=example:10m rate=1r/s;

    ssl_dhparam /etc/nginx/cert/dhparam.pem;
    ssl_ciphers "AES128+EECDH:AES128+EDH";
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    add_header X-Content-Type-Options nosniff;

    log_format main '[$time_local] $remote_addr - $remote_user $request" $status $body_bytes_sent $http_referer" "$http_user_agent" "$http_x_forwarded_for" $uid_got $uid_set';
    log_format up_status '[$time_local] $remote_addr "$request" status $bytes_sent $upstream_addr $upstream_status $upstream_cache_status $upstream_response_time $request_time';

    access_log /var/log/nginx/access.log main;
    charset utf8;
    client_max_body_size 5m;
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 6;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";
    gzip_http_version 1.1;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
    gzip_vary on;
    keepalive_timeout 65;
    large_client_header_buffers 8 32k;
    open_file_cache max=1000 inactive=20s;
    open_file_cache_min_uses 2;
    open_file_cache_valid 30s;
    proxy_set_header Request-Range "";
    real_ip_header X-Forwarded-For;
    send_timeout 300;
    sendfile on;
    server_names_hash_max_size 2048;
    server_tokens off;
    tcp_nodelay on;
    tcp_nopush on;
    proxy_buffer_size 16k;
    proxy_buffers 16 512k;
    proxy_busy_buffers_size 512k;
    proxy_busy_buffers_size 120;
    proxy_read_timeout 120;
    fastcgi_buffer_size 16k;
    fastcgi_buffers 16 512k;
    fastcgi_busy_buffers_size 512k;
    fastcgi_connect_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_temp_file_write_size 512k;
    include /etc/nginx/conf.d/*.conf;
}
EOF  

Generate dhparam file.

mkdir /etc/nginx/cert  
openssl dhparam -out /etc/nginx/cert/dhparam.pem 4096  

Also we need to make le-well-known.conf file to provide LE support.

cat << EOF > /etc/nginx/default.d/le-well-known.conf  
location ~ /.well-known {  
allow all;  
}
EOF  

Then configure default host for obtain SSL cert for it.

cp /etc/nginx/conf.d/default.conf.dist /etc/nginx/conf.d/default.conf  
nginx -t  
service nginx start  

Now you can test the nginx is working by going to http://example.com with your browser. If you can see nginx default start page then everything is good and we cant obtain certs.

Note: we must to use -a webroot option because LE client doesn't support nginx for now.

cd /opt/letsencrypt  
./letsencrypt-auto certonly -a webroot --webroot-path=/usr/share/nginx/html -d example.com -d www.example.com

Note: you can find cert into /etc/letsencrypt/live/example.com directory.

Nginx advanced configuration. Enable https support.

The next step - is deleting the default domain config and creating new one for our host.
Also for our new virtual domain we will enable redirecting from http to https to provide more security.

To enforce redundancy we will configure balancing.

rm -f /etc/nginx/conf.d/default.conf  
nginx -t  
service nginx restart  
cat << EOF > /etc/nginx/conf.d/myapp.example.com.conf  
upstream "balancer" {  
    least_conn;
    server backend-node-02.example.com:9000 weight=6; #backup;
    server 127.0.0.1:9000 weight=5 max_fails=1 fail_timeout=5s;
}
server {  
    listen 80;
    server_name example.com www.example.com;
    return 302 https://$server_name$request_uri;
}
server {  
    listen 443 ssl;
    limit_req zone=example burst=5;
    server_name example.com www.example.com;

    ssl_certificate /letsenxrypt/live/example.com/cert.pem;
    ssl_certificate_key /letsencrypt/live/example.com/privkey.pem;
    access_log /var/log/nginx/$host.access.log main;
    access_log /var/log/nginx/$host.upstream.log up_status;
    error_log /var/log/nginx/$host.error.log warn;
    charset utf-8;
    client_max_body_size 10m;

    location / {
        proxy_read_timeout 240;
        proxy_pass http://127.0.0.1:9000;
        add_header Access-Control-Allow-Origin *;
    }

    rewrite_log on;
    charset utf-8;
    client_max_body_size 10m;

    # static content example
    location ~* \.(jpg|jpeg|gif|png|js|css|ico|avi|pdf|swf|txt)$ {
        access_log off;
        expires max;
    }

    expires max;

    location / {
        if ($upstream_addr ~* '127.0.0.1') {
            set $upstream_hostname $host;
        }
        if ($upstream_addr !~ '127.0.0.1') {
            set $upstream_hostname example.com;
        }
        proxy_pass http://todo-web-app;
        proxy_set_header Host $upstream_hostname;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}
EOF  
root@example ~# nginx -t  
root@example ~# service nginx start  

Renew certificates

You can create easy cron task to auto-renew yours certificate every month.

Create LE config for renewing.

cat << EOF > /etc/letsencrypt/cli.ini  
authenticator = webroot  
webroot-path = /var/www/zombig.name  
server = https://acme-v01.api.letsencrypt.org/directory377  
renew-by-default  
agree-dev-preview  
agree-tos  
email = root@zombig.name  
EOF  

And add cron job.

crontab -e

@monthly /opt/letsencrypt/letsencrypt-auto --config /etc/letsencrypt/cli.ini -d zombig.name -d blog.zombig.name certonly && service nginx reload

Summary

Now we finished installation and configurtion for nginx and LE. We created virtual test domain configuration, obtain SSL cert from LE CA and enable https support for our test domain.
You can test how it works by typing http://example.com into your browser - and you will redirected to https://example.com. Note if you got error 502 Bad Gateway it's mean that yours backend is not working.

See more