Установка Discourse за обратным прокси с использованием рекомендованной (поддерживаемой) установки

В этой статье я покажу вам, как точно запустить Discourse за обратным прокси-сервером Nginx, используя при этом поддерживаемые методы установки.

Требования:

  1. Действительное доменное имя.
  2. Действительный SSL-сертификат.
  3. Действительные записи DNS, указывающие на доменное имя.
  4. Действительный рабочий почтовый сервер или провайдер SMTP.
  5. Рабочий обратный прокси-сервер Nginx и возможность доступа к экземпляру и создания SSL-сертификата для последующего использования.
  6. Виртуальная машина (VM) или контейнер LXC, способный запускать Docker.

В этом руководстве я буду использовать свой собственный экземпляр в качестве примера, со всеми работающими функциями, резервным копированием, обновлением и т. д. Протестировано в течение примерно 2 недель.

Спецификации моей виртуальной машины Discourse:

  • CPU: 4 ядра
  • RAM: 6 ГБ
  • Swap: 8 ГБ (файл подкачки на SSD)
  • Хранилище: 50 ГБ (SSD)
  • ОС: Ubuntu 22.04.3

Вы можете использовать минимальные требования, но при тестировании Discourse легко потребляет 2 ГБ. При неактивном использовании используется около 1,48 ГБ из 6 ГБ.

ПРИМЕЧАНИЕ: Эта установка использует обратный прокси-сервер для создания SSL-сертификатов. Если предпочтительно, можно использовать certbot.

ШАГ 1:

Скачайте последнюю версию Ubuntu Server с Get Ubuntu Server | Download | Ubuntu

ШАГ 2:

  1. Установите Ubuntu Server на шаблон VM/LXC.
    1.1 Убедитесь, что Ubuntu обновлена до последней версии со всеми пакетами сервера.
  2. Используйте следующие команды для установки всех необходимых пакетов:
apt update -y && apt upgrade -y && apt wget curl zip git docker.io nginx -y && reboot

ШАГ 3:
Установка Discourse. Следуйте Руководству для начинающих: discourse/docs/INSTALL-cloud.md at main · discourse/discourse · GitHub

Подключитесь к серверу по SSH и просто введите следующее:

sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
chmod 700 containers

После выполнения вышеуказанных действий запустите ./discourse-setup.

Следуйте всем шагам и вводите данные правильно, так как это критически важно для успешной установки.
Шаги будут запрашивать следующее; пример из руководства по настройке:

Дайте установке завершиться; это может занять некоторое время в зависимости от вашего интернет-соединения и характеристик сервера. На моей виртуальной машине установка заняла около 5–8 минут.

ШАГ 4:

После завершения установки вы увидите команду запуска контейнера Docker и сгенерированный ID контейнера (хеш).

ПРИМЕЧАНИЕ: Поскольку вы находитесь за прокси через WAN, вам будет показана ошибка 502, и поскольку контейнер использует сеть Docker, он будет недоступен из WAN или LAN, если только вы не подключитесь к сети 172.17.0.1/16, что нам не понадобится.

Еще раз проверьте, что установка завершена и контейнер Docker запущен, используя команду:

docker ps

Вы должны получить следующий вывод:

CONTAINER ID   IMAGE                 COMMAND        CREATED      STATUS       PORTS     NAMES
XXXXXX   local_discourse/app   "/sbin/boot"   6 days ago   Up 7 hours             app

ШАГ 5:
Обновление файла app.yml для соответствия конфигурации обратного прокси.

Откройте файл /var/discourse/container/app.yml в предпочитаемом текстовом редакторе.
Закомментируйте или удалите следующие строки (это будет обработано локальным Nginx):

  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

Непосредственно после строки #- "templates/web.letsencrypt.ssl.template.yml" добавьте следующее:

- "templates/web.socketed.template.yml"

Затем закомментируйте секцию expose, так как Discourse теперь будет использовать веб-сокеты, и Nginx сможет запуститься, освободив необходимые локальные порты 80 и 443.

#expose:
# - "80:80" # http
# - "443:443" # https

Теперь, чтобы заставить Discourse предоставлять ссылки только с HTTPS, добавьте следующее в секцию env:

# FORCE SSL 
DISCOURSE_FORCE_HTTPS: true

Теперь, чтобы все заработало, вам нужно пересобрать приложение. Используйте следующую команду:

cd /var/discourse
./launcher rebuild app

Дождитесь завершения. Чтобы проверить успешность, убедитесь, что команда запуска Docker отображается, или просто выполните docker ps, и вы увидите запущенный контейнер.

ШАГ 6:

Включите Nginx и настройте сайт по умолчанию для указания на контейнер Docker. Это позволит обратному прокси-серверу получать доступ к контейнеру через локальный Nginx.

Выполните следующую команду:

systemctl enable nginx && systemctl start nginx

Чтобы проверить, работает ли он, попробуйте открыть в браузере страницу приветствия Nginx по локальному IP-адресу.
Например:

http://10.10.0.4

Вы должны увидеть страницу поздравления от Nginx.

ШАГ 7:
Обновите конфигурацию по умолчанию следующим образом:
Сначала очистите файл конфигурации:

echo "" > /etc/nginx/sites-available/default

Откройте файл конфигурации в текстовом редакторе и добавьте следующее:
ПРИМЕЧАНИЕ: Замените server_name вашим доменом, а ssl_certificate и ssl_certificate_key — расположением вашего SSL-сертификата и ключа.
Можно использовать Certbot, но я просто синхронизировал (rsync) свой сертификат и ключ с моего обратного прокси-сервера.

server {
    listen 80; listen [::]:80;
    server_name add.yourdomain.com;
    server_tokens off;
    return 301 https://$host$request_uri;
}
# Default server configuration
#
server {
	# SSL configuration
	#
	server_tokens off;
	listen 443 ssl default_server;
	listen [::]:443 ssl default_server;

    server_name add.yourdomain.com;
	ssl_certificate /etc/ssl/certs/your_ssl_cert.bundle;
	ssl_certificate_key /etc/ssl/private/your_ssl_cert.key;
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM: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:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
	ssl_prefer_server_ciphers on;
	location / {
		proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock;
		proxy_set_header Host $http_host;
		proxy_http_version 1.1;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-Real-IP $remote_addr;
	}
}

Сохраните новый файл по умолчанию и выполните systemctl restart nginx.

ШАГ 8:

Откройте браузер и перейдите на домен вашего форума https://somedomain.com, и вы увидите следующую страницу.

Всё готово. Теперь вы можете следовать инструкциям для регистрации и начать работу вашего форума/сообщества :).

ДОПОЛНИТЕЛЬНЫЙ ШАГ:

Техническое обслуживание после установки
Мы настоятельно рекомендуем включить автоматические обновления безопасности для вашей ОС. В Ubuntu используйте команду dpkg-reconfigure -plow unattended-upgrades.

Для поддержки вы можете просто использовать это сообщество, а для локального Nginx вы можете задать вопросы в этом посте.

Это отлично. Спасибо.

Одно улучшение, которое я хотел бы увидеть в этом руководстве, — возможность раздачи статических файлов через nginx.

В настоящее время все запросы на статические файлы направляются в воркер в вышеуказанной конфигурации. Я всё ещё пытаюсь разобраться, как обойти это ограничение, и опубликую обновление, если мне удастся найти решение.

Не могли бы вы уточнить, о каких именно статических файлах идёт речь?

Возможно, вы имеете в виду статические страницы ошибок 404, 302, 500 от nginx или статические страницы от Discourse?

Я немного изучил этот вопрос и обнаружил, что перенаправление для страниц 404 работает. Единственная страница, которую мне не удаётся настроить — это страница ошибки 500.

Не могли бы вы быть немного более конкретны, чтобы я точно понимал, на что мне следует обратить внимание? :slight_smile:

Это руководство превосходное. Я смог пройти его целиком, пока не дошел до этапа загрузки веб-сайта. Возникает ошибка «слишком много перенаправлений». Когда я использую IP-адрес, Discourse загружается нормально, но проблема с перенаправлениями не решается. Почти получилось.

Буду признателен за любую помощь.

Спасибо.

Подскажите, пожалуйста, как можно решить эту проблему? Сайт работает, но в логе ошибок nginx контейнера я вижу следующие ошибки. Я использую обратный прокси, настроенный согласно приведённым выше инструкциям.

Просматривая файл /etc/nginx/conf.d/discourse.conf внутри контейнера, я вижу:

upstream discourse { server 127.0.0.1:3000; }

Не должно ли это быть что-то вроде?:  

upstream discourse { http://unix:/var/discourse/shared/standalone/nginx.http.sock; }

Просматривая /var/log/nginx внутри контейнера, я вижу ошибки вроде:

2025/02/09 21:00:21 [error] 69#69: *1 connect() failed (111: Connection refused) while connecting to upstream, client: xx.xx.xxx.xxx, server: _, request: "POST /message-bus/b39980c2387e4750bc1e320cb6195424/poll?dlp=t HTTP/1.1", upstream: "http://127.0.0.1:3000/message-bus/b39980c2387e4750bc1e320cb6195424/poll?dlp=t", host: "discourse.xxxx.com"

2025/02/09 21:00:23 [error] 67#67: *3 connect() failed (111: Connection refused) while connecting to upstream, client: xx.xx.xx.xxx, server: _, request: "GET /chat/api/me/channels HTTP/1.1", upstream: "http://127.0.0.1:3000/chat/api/me/channels", host: "discourse.xxxx.com"

2025/02/09 21:00:23 [error] 70#70: *5 connect() failed (111: Connection refused) while connecting to upstream, client: xx.xx.xx.xxx, server: _, request: "POST /message-bus/d95b6999d26242f28f4875732b195440/poll HTTP/1.1", upstream: "http://127.0.0.1:3000/message-bus/d95b6999d26242f28f4875732b195440/poll", host: "discourse.xxxx.com"

Спасибо!

Вы правильно выполнили шаг 5? Похоже, вы не используете сокет.

Да, я использую сокет на локальном обратном прокси вне контейнера, и да, я выполнил Шаг 5 в app.yml.

/var/discourse/containers# grep web.socketed.template.yml app.yml
  - "templates/web.socketed.template.yml"

/var/discourse/containers# grep templates/web.ssl.template.yml app.yml
  #  - "templates/web.ssl.template.yml"

/var/discourse/containers# grep templates/web.letsencrypt.ssl.template.yml app.yml
  #  - "templates/web.letsencrypt.ssl.template.yml"

/var/discourse/containers# grep http app.yml

#  - "80:80"   # http
#  - "443:443" # https

/var/discourse/containers# grep DISCOURSE_FORCE_HTTPS app.yml
DISCOURSE_FORCE_HTTPS: true
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  ## Раскомментируйте следующую строку, чтобы включить прослушивание IPv6
  #- "templates/web.ipv6.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Раскомментируйте эти две строки, если хотите добавить Lets Encrypt (https)
  #  - "templates/web.ssl.template.yml"
  #  - "templates/web.letsencrypt.ssl.template.yml"
  - "templates/web.socketed.template.yml"
## Какие TCP/IP-порты должен открывать этот контейнер?
## Если вы хотите, чтобы Discourse использовал порт совместно с другим веб-сервером, например Apache или nginx,
## см. https://meta.discourse.org/t/17247 для деталей
#expose:
#  - "80:80"   # http
#  - "443:443" # https

Я изучил это: Discourse working with jwilder /nginx proxy & acme-companion

но не могу понять, как это связано с моей проблемой…

Привет! Обновление от мая 2026 года для тех, кто это найдёт:

Я использую NPM Plus. Я следовал инструкциям здесь, но хотел добавить, если кто-то ещё использует NPM Plus:
Вместо того чтобы использовать ручную конфигурацию Nginx, указанную здесь, ИЛИ пользовательское расположение, ИЛИ расширенные настройки, вы можете просто использовать предоставленный Unix-URL без точки с запятой в конце и оставить порт пустым. Также полностью оставьте пустыми пользовательские расположения и расширенные настройки.

Смотрите изображение ниже для примера:

Я не пробовал это на обычном NPM, но это может или не может работать и с обычным/стандартным NPM.

Спасибо!