+++ categories = [ "Labs", "Infra" ] author = "kazu634" description = "nginx + Let's Encryptを用いてhttp/2環境を構築してみました。" tags = ["nginx"] date = "2016-01-11T23:32:47+08:00" title = "nginx + let's encryptを利用してHTTP/2を有効化した" +++ `nginx`+`Let's Encrypt`で`http/2`環境を構築したのでその時のメモです。 ## `nginx`のインストール・セットアップ ### `nginx`で必要になる前提パッケージ `libgeoip-dev`をインストールします: ``` % aptitude install libgeoip-dev ``` ### nginx-buildのインストール [cubicdaiya/nginx-build](https://github.com/cubicdaiya/nginx-build)をインストールします: ``` % wget https://github.com/cubicdaiya/nginx-build/releases/download/v0.6.5/nginx-build-linux-amd64-0.6.5.tar.gz ``` ### nginxのインストール 以下の手順で`nginx`をインストールします: ``` % 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`は以下の内容です: ``` #!/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`をセットアップします。 #### `nginx`起動に必要となるディレクトリ作成 `nginx`起動に必要となるディレクトリを作成します: ``` % sudo mkdir -p /var/lib/nginx/{body,fastcgi,proxy,scgi,uwsgi} ``` #### `nginx`設定ファイルなどのデプロイ `nginx`設定ファイルなどをデプロイします: ``` % 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`の中身は以下のとおりです: ``` 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`の設定ファイルの中身は以下のとおりです: ``` #!/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`の中身は以下のとおりです: ``` 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 Encyptを用いて証明書を取得する 以下の手順で[Let's Encrypt](https://letsencrypt.org/)から証明書を取得します: ``` % 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 すると以下のような注意書きが表示されます: ``` 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 ``` これで終わりです: ### Grade A+取得の道 [Qualys SSL Labs](https://www.ssllabs.com/index.html)で評価A+を目指した軌跡です。基本は[nginx - 我々はどのようにして安全なHTTPS通信を提供すれば良いか - Qiita](http://qiita.com/harukasan/items/fe37f3bab8a5ca3f4f92)を参考にして設定しています。 #### 暗号化スイートの選択 以下のように設定しました: ``` 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; ``` #### OCSP Staplingの設定 以下のように設定しました: ``` ssl_stapling on; ssl_stapling_verify on; resolver 8.8.4.4 8.8.8.8 valid=300s; resolver_timeout 10s; ``` #### ssl_dhparam 以下のコマンドを実行し、`DH鍵交換`に使用する暗号化ファイルを作成しました: ``` % sudo openssl dhparam -out /etc/letsencrypt/live/test.kazu634.com/dhparams_4096.pem 4096 ``` 作成後以下のように設定を実施しました: ``` ssl_dhparam /etc/letsencrypt/live/test.kazu634.com/dhparams_4096.pem; ``` #### HTTP Strict Transport Security(HSTS)の付与 以下のように設定を行います: ``` # Enable HSTS (HTTP Strict Transport Security) add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; ``` #### TLS Session Ticketsの有効化 以下のコマンドで鍵ファイルを作成します: ``` # openssl rand 48 > /etc/letsencrypt/live/test.kazu634.com/ticket.key ``` その後、以下のように設定を実施します: ``` ssl_session_tickets on; ssl_session_ticket_key /etc/letsencrypt/live/test.kazu634.com/ticket.key; ``` #### 結論 まとめると、以下のような`nginx`の設定ファイルを作成します: ``` 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)