Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53
0 (0)

Автор |  17/08/2026
Click to rate this post!
[Total: 0 Average: 0]

Є сервер з Ubuntu, на якому запущений один з наших сервісів. Сам сервіс ще в стадії Proof of Concept, тому в Kubernetes не переносимо – але ганяти трафік plain-text, тим більш коли це запити до AI Agents з реквестами та відповідями – ідея так собі.

Тому є задача додати NGINX як проксі до сервісів в Docker та налаштувати TLS/SSL.

Для отримання сертифіката використаємо Let’s Encrypt, а аби не морочитись кожні три місяці (чи вже місяць?) з renew – додамо автоматизацію апдейтів через AWS Route 53, бо сервер живе в AWS і домен ми тримаємо там жеж.

Сама схема з DNS validation через AWS Route 53 дуже стара, колись про неї писав в пості OpenVPN: Let’s Encrypt DNS verification с certbot и AWS Route53 и обновление сертификата в OpenVPN Access Server, але то було у 2019 році, і там не було NGINX.

A short note: взагалі, правильно казати TLS, а не SSL – бо остання версія SSL 3.0 була опублікована ще у 1996 році, а TLS – сучасна його заміна з актуальною на сьогодні версією 1.3 від 2018 року – див. RFC 8446. Але я більше звик до виразу “SSL”, тому буду казати або SSL, або TLS/SSL.

Установка NGINX на Ubuntu

Власне, маємо AWS EC2 з Ubuntu 24:

# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.4 LTS (Noble Numbat)"

Тут все стандартно – оновлюємо списки пакетів, з apt встановлюємо NGINX, запускаємо сервіс і додаємо в автостарт:

# apt update
# apt install -y nginx
# systemctl enable --now nginx

Перевіряємо локально, що все працює:

# curl -I http://127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)

Перевіряємо і при потребі відкриваємо порти 80 та 443 на AWS Security Group цього EC2:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Перевіряємо вже по домену і публічному IP:

# curl -I --connect-timeout 10 http://morpheus-agent.ops.example.co
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
...

Установка certbot для Let’s Encrypt

certbot – ACME-клієнт (ACME – Automatic Certificate Management Environment) для роботи з сертифікатами, який серед інших підтримує Let’s Encrypt Certificate Authority.

До самого certbot є багато плагінів, нам для роботи з AWS Route53 треба python3-certbot-dns-route53, і для NGINX – стандартний python3-certbot-nginx.

Встановлюємо пакети:

# apt install -y certbot python3-certbot-nginx python3-certbot-dns-route53

Перевіряємо сам клієнт:

# certbot --version
certbot 2.9.0

І плагіни:

# certbot plugins
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* dns-route53
Description: Obtain certificates using a DNS TXT record (if you are using AWS
Route53 for DNS).
...

* nginx
Description: Nginx Web Server plugin
...

Налаштування AWS IAM

Аби наш клієнт мав змогу вносити validation records в AWS Route 53 hosted DNS zone – йому, вочевидь, треба мати на це права.

Перевіряємо AWS EC2 Instance Profile зараз:

# aws --version
aws-cli/2.35.21 Python/3.14.6 Linux/6.17.0-1013-aws exe/x86_64.ubuntu.24
# aws sts get-caller-identity
{
    "UserId": "ARO***HHT:i-02569635c6284bb35",
    "Account": "492***148",
    "Arn": "arn:aws:sts::492***148:assumed-role/SSMInstanceProfile/i-02569635c6284bb35"
}

SSMInstanceProfile – дефолтна AWS IAM Role для AWS Systems Manager, яка, очікувано, не дає доступу до AWS Route 53:

# aws route53 list-hosted-zones-by-name \
  --query "HostedZones[?Name=='ops.example.co.' || Name=='example.co.'].[Name,Id]" \
  --output table

aws: [ERROR]: An error occurred (AccessDenied) when calling the ListHostedZonesByName operation: User: arn:aws:sts::492***148:assumed-role/SSMInstanceProfile/i-02569635c6284bb35 is not authorized to perform: route53:ListHostedZonesByName because no identity-based policy allows the route53:ListHostedZonesByName action

Тому створимо власну IAM Role із доступом до SSM та окремою DNS Zone.

Створення AWS IAM Role

Переходимо в AWS IAM > IAM Roles, додаємо нову роль.

В Trusted entity залишаємо дефолтне значення AWS service, в Use case – теж нічого не міняємо, залишаємо EC2:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Аби SSM продовжував працювати, відразу додаємо AWS Managed IAM Policy AmazonSSMManagedInstanceCore – а потім додамо власну для AWS Route 53:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Зберігаємо:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Створення IAM Policy для Route 53

Аби обмежити доступ окремою DNS-зоною – бо certbot виконує і додавання, і видалення записів, і не варто давати йому доступ всюди – в Route 53 знаходимо Hosted Zone ID:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Переходимо в IAM > Policies, створюємо нову.

Міняємо дефолтне значення:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

На нашу власну політику, яка дозволяє читання всіх зон в Route 53, але обмежує права на редагування тільки конкретною <HOSTED_ZONE_ID>:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DiscoverHostedZone",
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListHostedZonesByName"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ManageAcmeChallenge",
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/<HOSTED_ZONE_ID>",
      "Condition": {
        "ForAllValues:StringEquals": {
          "route53:ChangeResourceRecordSetsRecordTypes": [
            "TXT"
          ],
          "route53:ChangeResourceRecordSetsActions": [
            "CREATE",
            "UPSERT",
            "DELETE"
          ]
        }
      }
    },
    {
      "Sid": "CheckDnsChange",
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    }
  ]
}

Зберігаємо політику як, наприклад, CertbotRoute53DnsChallenge, повертаємо до IAM Role, яку створили вище, і через Attach Policies додаємо IAM Policy:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Переходимо до EC2, в Actions > Security > Modify IAM Role:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Підключаємо нову роль:

Ubuntu: установка NGINX та TLS-сертифікат з Let’s Encrypt та AWS Route53

Перевіряємо доступ до Route 53 тепер:

# aws route53 list-hosted-zones-by-name --query "HostedZones[?Name=='ops.example.co.'].[Name,Id]"
[
    [
        "ops.example.co.",
        "/hostedzone/Z02***OYY"
    ]
]

І тепер ми готові до налаштування NGINX.

Отримання TLS сертифікату з certbot

Отримуємо сертифікат, вказуємо опцію --dns-route53certbot сам “сходить” в AWS і створить всі необхідні записи для валідації домену:

# certbot certonly \
  --dns-route53 \
  --domain morpheus-agent.ops.example.co \
  --email [email protected] \
  --agree-tos \
  --no-eff-email
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for morpheus-agent.ops.example.co

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/morpheus-agent.ops.example.co/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/morpheus-agent.ops.example.co/privkey.pem

Перевіряємо, що certbot renew буде працювати:

# certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/morpheus-agent.ops.example.co.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account registered.
Simulating renewal of an existing certificate for morpheus-agent.ops.example.co

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:

Налаштування NGINX Virtual Host

Наш сервіс запущений в Docker на порту 3001:

# docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES                                               PORTS
...
app-hub                                             0.0.0.0:3001->3000/tcp, [::]:3001->3000/tcp
...

Створюємо файл /etc/nginx/sites-available/morpheus-agent.ops.example.co.

Стандартно – для запитів на порт 80 виконуємо редірект на 443/SSL, задаємо шляхи до сертифікату, в proxy_pass задаємо порт 3001 до нашого сервісу в Docker:

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

    server_name morpheus-agent.ops.example.co;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name morpheus-agent.ops.example.co;

    ssl_certificate /etc/letsencrypt/live/morpheus-agent.ops.example.co/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/morpheus-agent.ops.example.co/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    access_log /var/log/nginx/morpheus-agent.access.log;
    error_log /var/log/nginx/morpheus-agent.error.log;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

Завжди не любив цю схему в Ubuntu з ручними сімлінками, і зазвичай для себе роблю нормальну просто через файли конфігурації в /etc/nginx/conf.d – але тут нехай буде “дефолтний” варіант.

Створюємо symlink з /etc/nginx/sites-available/ на /etc/nginx/sites-enabled/:

# ln -s /etc/nginx/sites-available/morpheus-agent.ops.example.co \
  /etc/nginx/sites-enabled/morpheus-agent.ops.example.co

Перевіряємо синтаксис конфігурації, перечитуємо конфіги:

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

# systemctl reload nginx

Перевіряємо вже з HTTPS:

# curl -I https://morpheus-agent.ops.example.co
HTTP/1.1 404 Not Found

404 тут – нам ок, головне, що нема помилок SSL.

Ще можемо глянути з openssl – побачити більше інформації по нашому сертифікату:

# openssl s_client \
  -connect morpheus-agent.ops.example.co:443 \
  -servername morpheus-agent.ops.example.co \
  </dev/null 2>/dev/null |
openssl x509 -noout \
  -subject \
  -issuer \
  -dates \
  -serial \
  -fingerprint \
  -sha256 \
  -ext subjectAltName
subject=CN = morpheus-agent.ops.example.co
issuer=C = US, O = Let's Encrypt, CN = YE2
notBefore=Aug 11 13:32:07 2026 GMT
notAfter=Nov  9 13:32:06 2026 GMT
serial=06BA69E938D84355CCDF27689AA265DD9926
sha256 Fingerprint=11:E0:E0:D0:F6:A1:15:43:1B:A4:DE:DF:14:7A:23:63:AF:BA:71:1E:7C:CF:42:A1:CC:26:C9:44:BE:8C:F2:17
X509v3 Subject Alternative Name: 
    DNS:morpheus-agent.ops.example.co

Все працює, все готово.

Loading