Как развернуть и настроить статический сайт на Nginx в Ubuntu

Опубликовано:

В этом руководстве мы исходим из того, что вы уже авторизовались на VPS, обновили ОС, в случае использования UFW выполнили его первоначальную настройку. Если вы создавали пользователя, а не работаете под администратором системы, то перед большинством приводимых ниже команд надо писать sudo.

Установка Nginx, добавление профиля в UFW

Устанавливаем Nginx:

# apt install nginx

Проверяем наличие профилей Nginx в UFW:

# ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  Nginx QUIC
  OpenSSH

Включаем Nginx Full:

# ufw allow 'Nginx Full'

Проверяем, что входящий трафик для протокола SSH и веб-сервера Nginx позволен:

# ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere                  
Nginx Full                 ALLOW       Anywhere                  
OpenSSH (v6)               ALLOW       Anywhere (v6)             
Nginx Full (v6)            ALLOW       Anywhere (v6)

Проверяем, что Nginx запущен:

# systemctl status nginx

Если в выводе среди прочего вы видите active (running), значит это так. Чтобы выйти из вывода статуса службы, нажмите q.

При этом дефолтный сайт уже будет доступен по IP.

Сайт-заглушка веб-сервера Nginx, доступный по IP

Создание каталога сайта и загрузка файлов на сервер

Как и в случае с Apache файлы этого сайта находятся в каталоге /var/www/html/:

# ls /var/www/html/
index.nginx-debian.html

Для файлов своего сайта лучше создать отдельный каталог:

# mkdir /var/www/mysite

Загружаем сюда файлы с вашего локального компьютера, например с помощью утилиты rsync:

ivan@home:~$ rsync -avz site/ root@159.194.228.243:/var/www/mysite/

Также можно создать архив (об утилите tar), загрузить его с помощью scp, на сервере распаковать. Кроме этого, у Бегета для VPS доступен браузерный файловый менеджер:

Файловый менеджер Бегета. Файлы на VPS

Создание для сайта конфигурационного файла Nginx

В каталоге /etc/nginx/sites-available/ создаем конфигурационный файл для нашего сайта, который будет запускаться на Nginx. Сделать это можно с помощью vim:

# vim /etc/nginx/sites-available/mysite.conf

Пример начальных инструкций в файле, когда сертификат безопасности еще не выпущен:

server {
    listen 80;
    server_name mysite.name www.mysite.name;

    root /var/www/mysite;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Теперь надо создать ссылку в каталог sites-enabled:

# ln -s /etc/nginx/sites-available/mysite.conf /etc/nginx/sites-enabled/

Проверяем правильность конфигурации:

# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Перезапускаем веб-сервер:

# systemctl restart nginx

Привязка домена к серверу: настройка DNS-записей

Теперь для домена надо прописаны IP данного VPS. Если домен находится под управлением другого регистратора, а не там, где вы заказывали VPS, то у первого в настройках домена должны быть прописаны DNS-сервера второго. В панели хостера вы также создаете этот домен, и в его A-записях указываете IP. Сделать это надо и для домена без www, и с ним.

Редактирование A-записей домена. Изменение IP
Изменение IP домена в панели управления записями DNS у Бегет

После этого через небольшое время сайт должен открываться по имени домена, однако только по протоколу HTTP. Вам надо кликнуть в адресную строку браузера и исправить там https на http.

Запрет прямого доступа к серверу по IP-адресу в Nginx

Сейчас у вас на веб-сервере имеется два сайта: один доступен по IP, другой — по имени домена. По IP лучше устанавливать запрет доступа (403, Forbidden), чем настраивать редирект на домен или оставлять как есть. Поэтому следует открыть файл конфигурации дефолтного сайта (/etc/nginx/site-available/default) и оставить там только такие инструкции:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _;
    return 403;
}

Далее также проверяем безошибочность конфигурации и перезапускаем веб-сервер:

# sudo nginx -t
# systemctl reload nginx

При обращении по https к IP-адресу в Nginx часто используют сброс соединения без ответа (ошибка 444, "Страница недоступна", или "Не удалось получить доступ к сайту / ERR_CONNECTION_CLOSED"). Для этого в дефолтный файл конфигурации добавляют:

# Ловушка для HTTPS (порт 443) — сбросит соединение
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    # Нужен любой, даже самоподписанный сертификат, чтобы Nginx не ругался при старте
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    return 444; 
}

Сертификаты уже могут присутствовать в Ubuntu. Проверяем:

# ls -l /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key

Если нет, то устанавливаем утилиту и генерируем их:

# apt install ssl-cert -y && make-ssl-cert generate-default-snakeoil --force-overwrite

Конфигурацию следует проверить, сервер перезапустить.

Выпуск бесплатного SSL-сертификата Let's Encrypt через Certbot

Выпуск сертификата безопасности происходит также как для Apache, но используется ключ --nginx:

# apt install snapd
# snap install --classic certbot
# ln -s /snap/bin/certbot /usr/bin/certbot
# certbot --nginx -d mysite.name -d www.mysite.name

В конфигурационном файле дописываем http2 on, чтобы сайт передавался по протоколу h2. Итоговое содержание файла конфигурации:

server {
    server_name mysite.name www.mysite.name;

    root /var/www/mysite;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mysite.name/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mysite.name/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    http2 on;

}
server {
    if ($host = www.mysite.name) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = mysite.name) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name mysite.name www.mysite.name;
    return 404; # managed by Certbot
}

Если ваш VPS поддерживает IPv6, желательно внутри первого серверного блока (где listen 443 ssl;) дописать строчку для IPv6:

listen [::]:443 ssl;

А во второй блок (где listen 80;) дописать:

listen [::]:80;

Настройка 301-редиректа с WWW на домен без WWW в Nginx

Для редиректа с www на без-www надо добавить отдельный серверный блок (виртуальный хост), который будет обрабатывать запросы на www и выполнять редирект. При этом необходимо убрать упоминание домена с www из директивы server_name других блоков.

# ...
# Редирект с www на без-www (для HTTP и HTTPS)
server {
    listen 80;
    listen 443 ssl;
    server_name www.mysite.name;

    # Пути к SSL-сертификатам (скопируйте их из основного блока)
    ssl_certificate /etc/letsencrypt/live/mysite.name/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.name/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Сам редирект (код 301 — постоянный редирект)
    return 301 https://mysite.name$request_uri;
}

Однако в таком случае, с учетом команд в блоке порта 80, если страница будет запрашиваться по http и с www, то сначала произойдет редирект на https с www, и только потом на без-www. Поэтому оптимизированным решением будет сразу с порта 80 в обоих случаях перенаправлять на https без-www, а в серверном блоке, обрабатывающем www, оставить только протокол 443.

# ...
server {
    listen 80;
    server_name mysite.name www.mysite.name;
    return 301 https://mysite.name$request_uri;
}
server {
    listen 443 ssl;
    http2 on;
    server_name www.mysite.name;

    ssl_certificate /etc/letsencrypt/live/mysite.name/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mysite.name/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    return 301 https://mysite.name$request_uri;
}

Скрытие расширения .html, редирект на адрес без расширения

Чтобы Nginx читал файлы с расширением .html, когда адресация на сайте без них (ЧПУ — человеко-понятный URL), надо внести изменения в директиву location:

location / {
    # Было:
    # try_files $uri $uri/ =404;
    # Стало: сначала ищем точный файл, затем папку, затем файл с расширением .html, и если ничего нет — 404
    try_files $uri $uri/ $uri.html =404;
}

Чтобы при вводе .html в адресной строке происходил редирект на адрес без расширения, добавляем директиву частной маршрутизации:

location ~ \.html$ {
    if ($request_uri ~ ^/(.*)\.html$) {
        return 301 /$1;
    }
    try_files $uri =404;	
}

Поадресный редирект в Nginx

Если на сайте много перенаправлений, лучше всего составить карту редиректов с помощью директивы map.

# Создаем таблицу редиректов в самом начале файла (ВНЕ блока server)
map $request_uri $new_uri {
    default               ;
    
    # Левая колонка — старый адрес, правая — куда перенаправлять
    /kotlin0/null         /kotlin/null-operators;
    /pygame/              /pygame/course;
    # ... 
}

В location / до try_files помещаем условие перенаправления:

if ($new_uri != ) {
    return 301 $new_uri;
}

Сжатие и кэширование в Nginx

Чтобы работало сжатие, надо раскомментировать следующие строки в файле /etc/nginx/nginx.conf:

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # Оптимальный уровень сжатия (от 1 до 9)
gzip_buffers 16 8k;
gzip_http_version 1.1;

# Список типов файлов, которые ОБЯЗАТЕЛЬНО нужно сжимать:
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

Для настройки кэширования статики добавляют отдельный блок location с регулярным выражением, указав в нём типы кэшируемых файлов (изображения, стили и скрипты):

# Кэширование шрифтов, изображений, видео, стилей и скриптов на 1 месяц
location ~* \.(woff2|ttf|ttc|webp|png|jpeg|jpg|svg|gif|ico|webm|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
    access_log off;
}

Готовый пример конфигурационного файла Nginx для статического сайта

map $request_uri $new_uri {
    default               "";
    
    # Левая колонка — старый адрес, правая — куда перенаправлять
    /offer                    /policy/offer;
    /kotlin0/null             /kotlin/null-operators;
    /python/task/index        /python/task/;
}

server {
    listen 443 ssl;
    http2 on;    
    server_name mysite.name;

    root /var/www/mysite;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/mysite.name/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mysite.name/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location / {
        if ($new_uri != "") {
            return 301 $new_uri;
        }  
        # Защита от грязных ссылок, которые ведут на страницы с параметрами, которых не существует
        # Если в аргументах (параметрах URL) есть page= с любым значением
        if ($args ~* "(^|&)page=") {
    	    return 404;
        }
        try_files $uri $uri/ $uri.html =404;
    }

    location ~ \.html$ {
    	if ($request_uri ~ ^/(.*)\.html$) {
            return 301 /$1;
   	    }
        try_files $uri =404;	
    }	    

    location ~* \.(woff2|ttf|ttc|webp|png|ico|webm|css|js)$ {
        expires 30d;
    	add_header Cache-Control "public, no-transform";
    	access_log off;
        log_not_found off;
        try_files $uri =404;
    }
}
server {
    listen 80;
    server_name mysite.name www.mysite.name;
    return 301 https://mysite.name$request_uri;
}
server {
    listen 443 ssl;
    http2 on;
    server_name www.mysite.name;

    ssl_certificate /etc/letsencrypt/live/mysite.name/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mysite.name/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    return 301 https://mysite.name$request_uri;
}