Программы
Как добавить HTTPS в nginx на Ubuntu Server (16.04 и выше)

Как добавить HTTPS в nginx на Ubuntu Server (16.04 и выше)

Наличие HTTPS на веб-сайте долгое время считалось роскошью. Тому много причин: особо за безопасность владельцы блогов и «визиток» не парились, а сам сертификат стоил денег (что-то в районе $100 в год).

Наличие HTTPS на веб-сайте долгое время считалось роскошью. Тому много причин: особо за безопасность владельцы блогов и «визиток» не парились, а сам сертификат стоил денег (что-то в районе $100 в год).

Но всё больше в последнее время давление на владельцев веб-ресурсов:

  • Гугл обещал поднять в выдаче сайты с HTTPS;
  • Браузеры ругаются на страницы полями пароля без HTTPS;
  • В конце концов сертификаты стали бесплатными.

Зачем нужен сертификат сайту

Подробнее остановимся на последнем. Всё ещё можно купить сертификат, в котором будет указано, что домен подписан сертификатом, который принадлежит компании «Roga & Kopyta Int.». Но надо ли это?

Сервис Let’s Encrypt предлагает бесплатные сертификаты типа «Этот сетрификат выдан этому домену». То есть по сути гарантирует лишь то, что никакой шутник не подменил сайт, а значит данные пересылаются безопасно. Большинству сайтов такой защиты — за глаза!

Настраиваем Let’s Encrypt на nginx в Ubuntu

Данный способ несколько раз проверен на PHP-сайтах (в том числе wordpress) и Django-сайтах (через uwsgi).

Для начала установим необходимые пакеты для добавления ppa-репозиториев:

apt update
apt install -y \
    python-software-properties software-properties-common

Добавим программу certbot, которая будет обновлять нам сертификаты. Замечу, что Let’s Encrypt выдаёт сертификаты на 2 месяца, поэтому его надо обновлять автоматически — этим и займётся certbot.

add-apt-repository ppa:certbot/certbot
apt update

Ну и поставим сам certbot — репозиторий его ведь добавили!

apt install -y \
    certbot

Дальше нам понадобиться добавить путь, по которому сервис Let’s Encrypt будет проверять — мы ли это.

server {
    ...

    # Let's Encrypt
    location ^~ /.well-known/acme-challenge/ {
        root /path/to/static/;
        add_header Cache-Control public;
        allow all;
    }
    ...
}

Многие руководства советуют добавлять location /.well-known, но certbot и letsencrypt используют именно /.well-known/acme-challenge. В связи с этим у меня были определённые трудности, когда я это настраивал для Django-сервиса.

Перезагрузим конфиги nginx:

systemctl reload nginx

И теперь мы можем уже запустить генерацию сертификатов:

certbot certonly -a webroot --webroot-path=/path/to/static/ -d имя-домена.ru -d www.имя-домена.ru

В этот момент certbot положит специальные файлы в /path/to/static, которые будут доступны по урлу http://имя-домена.ru/.well-know/acme-challenge. Сервер let’s encrypt сходит по этому url и проверит — мы ли это, никто не подменил наш сайт. После того, как проверка завершится, certbot положит сертификаты, подписанные letsencrypt в директорию /etc/letsrncrypt/live/имя-домена.ru (их мы и будем использовать).

После чего мы можем включить https:

server {
    listen 443 ssl;

    server_name имя-домена.ru
                www.имя-домена.ru;

    # SSL cert
    ssl_certificate /etc/letsencrypt/live/имя-домена.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/имя-домена.ru/privkey.pem;

    # Let's Encrypt
    location ^~ /.well-known/acme-challenge/ {
        root /path/to/static/;
        add_header Cache-Control public;
        allow all;
    }
}

И снова перечитаем конфигурацию nginx:

systemctl reload nginx

Теперь проверяем — работает ли https (заходим на сайт по https).

Ну и чтобы всегда использовать https — настроим перенаправление с http на https:

server {
    listen 80;
    server_name имя-домена.ru
                www.имя-домена.ru;
    return 301 https://имя-домена.ru$request_uri;
    access_log off;
}

В очередной раз перечитаем конфигурацию nginx:

systemctl reload nginx

Теперь должно всё работать.

Забодьтесь о безопасности своих пользователей — теперь это ещё и «бесплатно».

Ответы на вопросы читателей

Q: Я правильно понимаю, что бот certbot будет сам заходить на сервис Let’s Encrypt и продлевать сертификаты каждые 2 месяца?

A: Современные версии certbot добавляют в crontab запись для автоматического обновления.

Если же не хотите пологаться на то, что кто-то позаботится об обновлении, можете добавить сами в cron команду /usr/bin/letsencrypt renew && nginx -s reload. Раз в день / неделю – впролне себе нормально. Например, от юзера, у которого есть права на letsencrypt renew и nginx -s reload выполнить:

echo "0 0 * * * /usr/bin/letsencrypt renew && nginx -s reload" | crontab

– каждый день в полночь по времени сервера.

Q: Эту /.well-known/acme-challenge/ директорию надо создавать или нет?

A: В описанном location есть директива root. Она указывает на директорию, куда будет направлять данный URL. Эту же директорию мы указываем certbot-у как --webroot-path - он её сам создаст и будет генерить нужные файлы для подтверждения, что это действительно тот домен, которым представляется.

Q: Ладно, все заработало - но есть нюанс - по http показывает обычный рабочий сайт, но стоит зайти по https - выдает заглушку nginx - типа он установлен бла-бла-бла

A: Нужно все локейшены продублировать в сервере, который с https (тот что listen 443), ведь это отдельный сервер, который сидит на отдельном порту.

Есть вариант всё это proxy_pass-ом завернуть на http-шный сервер, но я бы советовал нормально все локейшены и директивы а ля "gzip on" и тд перенести в https сервер. После этого - проверить и сам http редиректить на https. Например, вот так:

server {
  listen 80;
  server_name 900913.ru
              www.900913.ru;
  return 301 https://900913.ru$request_uri;
  access_log off;
}

Вообще, весь конфиг сайта может выглядеть вот так (в примере – uwsgi-приложение):

# Редирект с http на https
server {
    listen 80;
    server_name 900913.ru
                www.900913.ru;
    return 301 https://900913.ru$request_uri;
    access_log off;
}

# Редирект с www на домен без www
server {
    listen 443 ssl;
    server_name www.900913.ru;
    return 301 https://900913.ru$request_uri;
    ssl_certificate /path/to/file/fullchain.pem;
    ssl_certificate_key /path/to/file/privkey.pem;
    access_log off;
}

server {
    listen 443 ssl;
    server_name 900913.ru;

    ssl_certificate /path/to/file/fullchain.pem;
    ssl_certificate_key /path/to/file/privkey.pem;

    gzip on;
    gzip_comp_level 7;
    gzip_vary on;
    gzip_static on;
    gzip_types  text/plain text/css text/javascript application/javascript
                application/x-javascript image/svg+xml image/png image/jpeg;

    client_max_body_size 20m;
    fastcgi_pass_request_body on;

    # Let's Encrypt
    location ^~ /.well-known/acme-challenge/ {
        root /certbot/webroot-path/;
        add_header Cache-Control public;
        allow all;
    }

    location ~ /\. {
        deny  all;
    }

    location = /robots.txt {
        alias /path/to/file/robots.txt;
    }

    # static
    location ~* \.(html|txt|jpg|jpeg|gif|png|pdf|ico|css|bmp|js|swf|otf|woff|ttf|gz|svg|ogg)$ {
        root /path/to/static-files;
        expires 1M;
        add_header Cache-Control public;
    }

    location / {
        uwsgi_pass 127.0.0.1:{{ UWSGI PORT }};
        include uwsgi_params;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $http_host;
    }
}