nginx + let's encryptを利用してHTTP/2を有効化した

nginx+Let's Encrypthttp/2環境を構築したのでその時のメモです。

libgeoip-devをインストールします:

1
% aptitude install libgeoip-dev

cubicdaiya/nginx-buildをインストールします:

1
% wget https://github.com/cubicdaiya/nginx-build/releases/download/v0.6.5/nginx-build-linux-amd64-0.6.5.tar.gz

以下の手順でnginxをインストールします:

1
2
3
4
5
6
7
% vi configure.sh

% ./nginx-build -d temp -v 1.9.9 -c configure.sh -zlib -pcre -openssl

% cd temp/nginx/1.9.9/nginx-1.9.9

% sudo make install

なお、configure.shは以下の内容です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

./configure --with-cc-opt='-g -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2' ¥
            --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log ¥
            --error-log-path=/var/log/nginx/error.log  --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid ¥
            --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi ¥
            --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi ¥
            --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module ¥
            --with-http_v2_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module ¥
            --with-http_addition_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module ¥
            --with-http_sub_module

nginxをセットアップします。

nginx起動に必要となるディレクトリを作成します:

1
% sudo mkdir -p /var/lib/nginx/{body,fastcgi,proxy,scgi,uwsgi}

nginx設定ファイルなどをデプロイします:

1
2
3
4
5
6
7
8
% sudo mkdir -p /etc/nginx/{sites-available,sites-enabled}

% vi /etc/nginx/nginx.conf

% vi /etc/init.d/nginx
% sudo update-rc.d nginx defaults

% vi /etc/nginx/sites-enabled/default

nginx.confの中身は以下のとおりです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
user www-data;

# Newer version of Nginx calculates the worker_processes,
# based on the CPU cores. Use this feature:
worker_processes auto;

pid /run/nginx.pid;

# number of file descriptors used for nginx
# the limit for the maximum file descriptors on the server is usually set by the OS.
# if you don't set FD's then OS settings will be used.
worker_rlimit_nofile 100000;

events {
    # determines how much clients will be served per worker
    # max clients = worker_connections * worker_processes
    # max clients is also limited by the number of socket connections available on the system (~64k)
    worker_connections 4096;

    # accept as many connections as possible
    multi_accept on;

    # mutex config:
    accept_mutex on;
    accept_mutex_delay 100ms; # default: 500 -> 100 ms

    # optmized to serve many clients with each thread, essential for linux
    use epoll;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;
    types_hash_max_size 2048;

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

    server_names_hash_bucket_size  128;

    # cache informations about FDs, frequently accessed files
    # can boost performance:
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # allow the server to close connection on non responding client,
    # this will free up memory
    reset_timedout_connection on;

    # request timed out -- default 60
    client_body_timeout 10s;

    # if client stop responding, free up memory -- default 60
    send_timeout 2s;

    ##
    # Logging Settings
    ##

    log_format ltsv "time:$time_local\thost:$remote_addr"
                    "\tforwardedfor:$http_x_forwarded_for\t"
                    "method:$request_method\tpath:$request_uri\tprotocol:$server_protocol"
                    "\tstatus:$status\tsize:$body_bytes_sent\treferer:$http_referer"
                    "\tua:$http_user_agent\ttaken_sec:$request_time"
                    "\tbackend:$upstream_addr\tbackend_status:$upstream_status"
                    "\tcache:$upstream_http_x_cache\tbackend_runtime:$upstream_response_time"
                    "\tvhost:$host";

    access_log /var/log/nginx/access.log ltsv;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";
    gzip_min_length 10240;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

nginxの設定ファイルの中身は以下のとおりです:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/bin/sh

### BEGIN INIT INFO
# Provides:       nginx
# Required-Start:    $local_fs $remote_fs $network $syslog $named
# Required-Stop:     $local_fs $remote_fs $network $syslog $named
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/share/nginx/sbin/
DAEMON=/usr/share/nginx/sbin/nginx
NAME=nginx
DESC=nginx

# Include nginx defaults if available
if [ -r /etc/default/nginx ]; then
        . /etc/default/nginx
fi

STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"

test -x $DAEMON || exit 0

. /lib/init/vars.sh
. /lib/lsb/init-functions

# Try to extract nginx pidfile
PID=$(cat /etc/nginx/nginx.conf | grep -Ev '^\s*#' | awk 'BEGIN { RS="[;{}]" } { if ($1 == "pid") print $2 }' | head -n1)
if [ -z "$PID" ]
then
        PID=/run/nginx.pid
fi

# Check if the ULIMIT is set in /etc/default/nginx
if [ -n "$ULIMIT" ]; then
        # Set the ulimits
        ulimit $ULIMIT
fi

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \
                $DAEMON_OPTS 2>/dev/null \
                || return 2
}

test_nginx_config() {
        $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PID --name $NAME
        RETVAL="$?"

        sleep 1
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        start-stop-daemon --stop --signal HUP --quiet --pidfile $PID --name $NAME
        return 0
}

#
# Rotate log files
#
do_rotate() {
        start-stop-daemon --stop --signal USR1 --quiet --pidfile $PID --name $NAME
        return 0
}

#
# Online upgrade nginx executable                                                                                                                                                                                                                                      [67/1833]
#
# "Upgrading Executable on the Fly"
# http://nginx.org/en/docs/control.html
#
do_upgrade() {
        # Return
        #   0 if nginx has been successfully upgraded
        #   1 if nginx is not running
        #   2 if the pid files were not created on time
        #   3 if the old master could not be killed
        if start-stop-daemon --stop --signal USR2 --quiet --pidfile $PID --name $NAME; then
                # Wait for both old and new master to write their pid file
                while [ ! -s "${PID}.oldbin" ] || [ ! -s "${PID}" ]; do
                        cnt=`expr $cnt + 1`
                        if [ $cnt -gt 10 ]; then
                                return 2
                        fi
                        sleep 1
                done
                # Everything is ready, gracefully stop the old master
                if start-stop-daemon --stop --signal QUIT --quiet --pidfile "${PID}.oldbin" --name $NAME; then
                        return 0
                else
                        return 3
                fi
        else
                return 1
        fi
}

case "$1" in
        start)
                [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
                do_start
                case "$?" in
                        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
                esac
                ;;
        stop)
                [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
                do_stop
                case "$?" in
                        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
                esac
                ;;
        restart)
                log_daemon_msg "Restarting $DESC" "$NAME"

                # Check configuration before stopping nginx
                if ! test_nginx_config; then
                        log_end_msg 1 # Configuration error
                        exit 0
                fi

                do_stop
                case "$?" in
                        0|1)
                                do_start
                                case "$?" in
                                        0) log_end_msg 0 ;;
                                        1) log_end_msg 1 ;; # Old process is still running
                                        *) log_end_msg 1 ;; # Failed to start
                                esac
                                ;;
                        *)
                                # Failed to stop
                                log_end_msg 1
                                ;;
                esac
                ;;
        reload|force-reload)
                log_daemon_msg "Reloading $DESC configuration" "$NAME"

                # Check configuration before reload nginx
                #
                # This is not entirely correct since the on-disk nginx binary
                # may differ from the in-memory one, but that's not common.
                # We prefer to check the configuration and return an error
                # to the administrator.
                if ! test_nginx_config; then
                        log_end_msg 1 # Configuration error
                        exit 0
                fi

                do_reload
                log_end_msg $?
                ;;
        configtest|testconfig)
                log_daemon_msg "Testing $DESC configuration"
                test_nginx_config
                log_end_msg $?
                ;;
        status)
                status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $?
                status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $?
                ;;
        upgrade)
                log_daemon_msg "Upgrading binary" "$NAME"
                do_upgrade
                log_end_msg 0
                ;;
        rotate)
                log_daemon_msg "Re-opening $DESC log files" "$NAME"
                do_rotate
                log_end_msg $?
                ;;
        *)
                echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade}" >&2
                exit 3
                ;;
esac

:

/etc/nginx/sites-enabled/defaultの中身は以下のとおりです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
  # allow access from localhost
  listen 80 reuseport backlog=1024;
  server_name test.kazu634.com;

  root /usr/share/nginx/html;
  index index.html index.htm;

  location / {
    gzip on;
    gzip_types text/css text/javascript;
    gzip_vary on;

    gzip_static always;
    gunzip on;

    try_files $uri $uri/ /index.html;

  }
}

# Denies the access without the pre-defined virtual host.
server {
  listen 80 default_server;
  server_name _;

  return 444;
}

以下の手順でLet’s Encryptから証明書を取得します:

1
2
3
4
5
% git clone https://github.com/letsencrypt/letsencrypt

% cd letsencrypt

% ./letsencrypt-auto certonly --webroot -d test.kazu634.com --webroot-path /usr/share/nginx/html/

途中で以下のダイアログが表示されます。ひとつ目のダイアログではメールアドレスを入力します:

1. tmux

次のダイアログでは[Agree]ボタンを押します:

2. tmux

すると以下のような注意書きが表示されます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to simoom634@yahoo.co.jp.
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/test.kazu634.com/fullchain.pem. Your cert
   will expire on 2016-04-02. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

これで終わりです:

Qualys SSL Labsで評価A+を目指した軌跡です。基本はnginx - 我々はどのようにして安全なHTTPS通信を提供すれば良いか - Qiitaを参考にして設定しています。

以下のように設定しました:

1
2
3
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA;
  ssl_prefer_server_ciphers on;

以下のように設定しました:

1
2
3
4
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.4.4 8.8.8.8 valid=300s;
  resolver_timeout 10s;

以下のコマンドを実行し、DH鍵交換に使用する暗号化ファイルを作成しました:

1
% sudo openssl dhparam -out /etc/letsencrypt/live/test.kazu634.com/dhparams_4096.pem 4096

作成後以下のように設定を実施しました:

1
  ssl_dhparam /etc/letsencrypt/live/test.kazu634.com/dhparams_4096.pem;

以下のように設定を行います:

1
2
  # Enable HSTS (HTTP Strict Transport Security)
  add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

以下のコマンドで鍵ファイルを作成します:

1
# openssl rand 48 > /etc/letsencrypt/live/test.kazu634.com/ticket.key

その後、以下のように設定を実施します:

1
2
  ssl_session_tickets on;
  ssl_session_ticket_key /etc/letsencrypt/live/test.kazu634.com/ticket.key;

まとめると、以下のようなnginxの設定ファイルを作成します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
server {
  # allow access from localhost
  # listen 80 reuseport backlog=1024;
  listen 443 ssl http2 backlog=1024;
  server_name test.kazu634.com;

  ssl_certificate /etc/letsencrypt/live/test.kazu634.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/test.kazu634.com/privkey.pem;
  ssl_dhparam /etc/letsencrypt/live/test.kazu634.com/dhparams_4096.pem;

  ssl_session_cache   shared:SSL:3m;
  ssl_buffer_size     4k;
  ssl_session_timeout 10m;

  ssl_session_tickets on;
  ssl_session_ticket_key /etc/letsencrypt/live/test.kazu634.com/ticket.key;

  ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA;
  ssl_prefer_server_ciphers on;

  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.4.4 8.8.8.8 valid=300s;
  resolver_timeout 10s;

  # Enable HSTS (HTTP Strict Transport Security)
  add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

  root /var/www/blog_cache;
  index index.html index.htm;

  location / {
    gzip on;
    gzip_types text/css text/javascript;
    gzip_vary on;

    gzip_static always;
    gunzip on;

    try_files $uri $uri/ /index.html;

  }
}

# Denies the access without the pre-defined virtual host.
server {
  listen 80 default_server;
  server_name _;

  return 444;
}

SSL Server Test_ test.kazu634.com (Powered by Qualys SSL Labs)