—Заметки

Свой Speedtest с HTTPS на Ubuntu (LibreSpeed + nginx + Let's Encrypt)

Инструкция поднимает собственный веб-спидтест на сервере Ubuntu с валидным HTTPS-сертификатом.

Работает на любом порту, в том числе когда порты 80 и 443 заняты (например, роутером MikroTik), потому что сертификат выпускается через DNS-challenge (Cloudflare) и входящие порты для этого не нужны.

Как это работает

Браузер ──HTTPS:8080──▶ nginx (TLS) ──HTTP──▶ 127.0.0.1:8081 ──▶ контейнер LibreSpeed

Что понадобится

  1. Сервер Ubuntu 22.04 / 24.04 с root-доступом (хватает 2 CPU и 1–2 ГБ RAM для канала до ~1 Гбит/с).
  2. Домен, DNS которого управляется в Cloudflare, и A-запись speedtest.example.com → публичный IP сервера (в Cloudflare поставьте режим DNS only, серое облако: прокси Cloudflare не пропускает такие порты и ограничивает размер загрузки).
  3. Если сервер за роутером: проброс выбранного TCP-порта на сервер (см. раздел «Роутер»).

Переменные

Дальше везде используются эти значения. Задайте свои:

Что Пример Комментарий
Домен speedtest.example.com должен быть в Cloudflare
Внешний порт HTTPS 8080 любой свободный
Внутренний порт контейнера 8081 должен отличаться от внешнего

1. Подготовка: чистим старые прокси (если есть)

Нужно, только если на сервере раньше стояли nginx или Caddy. purge удаляет их конфиги, сохраните нужное заранее.

sudo -i
systemctl disable --now nginx caddy 2>/dev/null
apt purge -y 'nginx*' caddy
apt autoremove -y
rm -rf /etc/nginx /etc/caddy /var/lib/caddy

Проверьте, что нужные порты свободны:

ss -tlnp | grep -E ':8080 |:8081 '

Вывод должен быть пустым.

2. Токен Cloudflare

В Cloudflare: My Profile → API Tokens → Create Token → Create Custom Token.

Токен нигде не публикуйте и не отправляйте в чаты, вводите только на сервере.

3. Установка LibreSpeed в Docker

apt update && apt install -y docker.io docker-compose-v2
systemctl enable --now docker
mkdir -p ~/speedtest && cd ~/speedtest
cat > docker-compose.yml <<'EOF'
services:
  speedtest:
    container_name: speedtest
    image: ghcr.io/librespeed/speedtest:latest
    restart: always
    environment:
      MODE: standalone
      TITLE: "My Speedtest"
      TELEMETRY: "false"
    ports:
      - "127.0.0.1:8081:8080"
EOF
docker compose up -d
curl -sI http://127.0.0.1:8081 | head -1

Последняя команда должна вывести HTTP/1.1 200 OK.

4. Установка nginx

apt install -y nginx
rm -f /etc/nginx/sites-enabled/default
mkdir -p /etc/nginx/ssl

5. Получение сертификата (DNS-challenge)

curl https://get.acme.sh | sh -s email=you@example.com
source ~/.bashrc
acme.sh --set-default-ca --server letsencrypt

Задайте домен и токен и выпустите сертификат:

export DOMAIN="speedtest.example.com"
export CF_Token="ВАШ_ТОКЕН"
acme.sh --issue --dns dns_cf -d "$DOMAIN"

Установка сертификата для nginx. После каждого продления nginx перезагрузится сам:

acme.sh --install-cert -d "$DOMAIN" \
  --key-file       /etc/nginx/ssl/speedtest.key \
  --fullchain-file /etc/nginx/ssl/speedtest.crt \
  --reloadcmd      "systemctl reload nginx"

Автопродление настроено через cron при установке acme.sh, проверить: acme.sh --list.

6. HTTPS-конфиг nginx

cat > /etc/nginx/conf.d/speedtest.conf <<'EOF'
server {
    listen 8080 ssl;
    server_name SPEEDTEST_DOMAIN;

    ssl_certificate     /etc/nginx/ssl/speedtest.crt;
    ssl_certificate_key /etc/nginx/ssl/speedtest.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # для теста upload
    client_max_body_size 0;
    proxy_request_buffering off;

    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_http_version 1.1;
        proxy_buffering off;
        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;
    }
}
EOF
sed -i "s/SPEEDTEST_DOMAIN/$DOMAIN/" /etc/nginx/conf.d/speedtest.conf
nginx -t && systemctl enable --now nginx && systemctl restart nginx

Другие порты: замените 8080 в listen на нужный внешний, а 8081 в proxy_pass на внутренний порт контейнера (из docker-compose.yml).

7. Файрвол сервера

ufw allow 8080/tcp

8. Проверка

Локально на сервере:

ss -tlnp | grep -E ':8080 |:8081 '
curl -I --resolve "$DOMAIN:8080:127.0.0.1" "https://$DOMAIN:8080"

Ожидаемо: 127.0.0.1:8081 у docker-proxy, 0.0.0.0:8080 у nginx, ответ HTTP/1.1 200 OK.

Снаружи откройте в браузере https://speedtest.example.com:8080/. Замок должен быть зелёным, предупреждений нет.

Роутер (если сервер за NAT)

Нужен проброс выбранного внешнего порта на сервер. Пример для MikroTik, подставьте IP сервера в локальной сети:

/ip firewall nat add chain=dstnat protocol=tcp dst-port=8080 in-interface-list=WAN action=dst-nat to-addresses=192.168.X.X to-ports=8080 comment="speedtest https"
/ip firewall filter add chain=forward protocol=tcp dst-port=8080 dst-address=192.168.X.X action=accept place-before=0 comment="speedtest https"

Убедитесь, что этот порт не занят самим роутером (WebFig, API и т.п.).

Частые проблемы

Симптом Причина и решение
curl: (35) wrong version number На порту отвечает не nginx, а контейнер напрямую (HTTP). Проверьте ports: в docker-compose.yml: должно быть 127.0.0.1:8081:8080. Затем docker compose up -d --force-recreate и systemctl restart nginx.
bind() to 0.0.0.0:8080 failed Порт занят другим процессом: ss -tlnp | grep :8080.
acme.sh не находит зону / ошибка авторизации Проверьте права токена (DNS Edit и Zone Read) и что домен действительно в Cloudflare.
Сертификат не продлевается Токен истёк или удалён. Создайте новый, выполните export CF_Token=... и acme.sh --renew -d "$DOMAIN" --force.
Скорость меньше ожидаемой Проверьте загрузку CPU (top -d 1) во время теста, канал сервера и роутер (NAT и conntrack на MikroTik).
Нужен HTTPS «без домена» Let's Encrypt не выдаёт сертификаты на голый IP. Можно сделать самоподписанный сертификат через openssl, но браузер покажет предупреждение.

Обновление и удаление

Обновить LibreSpeed:

cd ~/speedtest && docker compose pull && docker compose up -d

Удалить всё:

cd ~/speedtest && docker compose down && cd ~ && rm -rf ~/speedtest
apt purge -y 'nginx*' && rm -rf /etc/nginx
acme.sh --uninstall

И удалить A-запись и токен в Cloudflare.

← ко всем записям