Discourse 无法在 Apache 和代理重定向下加载

过去几天我一直在尝试寻找一种在同一个 DigitalOcean Droplet 上将 Discourse 与 Apache 搭配使用的方法,但 Discourse 上提供的教程要么过时,要么不适用于我。其中一个教程使用的是 CentOS 和 HAProxy(我使用的是 Ubuntu),另一个则使用 Nginx(我使用的是 Apache)。

我在 DigitalOcean 的一个讨论串中看到了一条评论,并在测试 Droplet 上按照那里的说明操作:Install Discourse on a droplet with WordPress served by Apache ? | DigitalOcean

一切似乎都很顺利,包括 Let’s Encrypt SSL 证书。我的主页和一些静态 HTML 文档运行正常。Discourse 虽然安装时没有报错,但无法显示。访问 community.mysite.com 时,我只看到主页,且 URL 显示为不安全的 HTTPS。是的,我的 DNS 设置准确无误,并指向了正确的服务器。

这是我的 mysite.com.conf 文件:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName mysite.com
    ServerAlias www.mysite.com
    DocumentRoot /var/www/mysite.com
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =www.mysite.com [OR]
    RewriteCond %{SERVER_NAME} =mysite.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

这是我的 mysite.com-le-ssl.conf 文件:

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    ServerName mysite.com
    ServerAlias www.mysite.com
    DocumentRoot /var/www/mysite.com
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/mysite.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/mysite.com/privkey.pem
</VirtualHost>
</IfModule>

这是我的 community.mysite.com.conf 文件:

<VirtualHost *:80>
  ServerName community.mysite.com
  ServerAlias www.community.mysite.com

  <IfModule proxy_module>
    ProxyPreserveHost on
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
  </IfModule>
</VirtualHost>

最后,这是我的 app.yml 文件(已编辑):

## 这是 Discourse 的一体化独立 Docker 容器模板
##
## 修改此文件后,您必须重新构建
## /var/discourse/launcher rebuild app
##
## 编辑时请*非常*小心!
## YAML 文件对空白字符或缩进错误极其敏感!
## 如有需要,请访问 http://www.yamllint.com/ 验证此文件

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## 如果希望添加 Let's Encrypt (https),请取消注释以下两行
  - "templates/web.ssl.template.yml"
  - "templates/web.letsencrypt.ssl.template.yml"

## 此容器应暴露哪些 TCP/IP 端口?
## 如果您希望 Discourse 与另一个 Web 服务器(如 Apache 或 Nginx)共享端口,
## 请参阅 https://meta.discourse.org/t/17247 了解详情
expose:
      - "8080:80"   # 将主机端口 8080 转发到容器端口 80 (http)
      - "8443:443"   # 将主机端口 8443 转发到容器端口 443 (https)

params:
  db_default_text_search_config: "pg_catalog.english"

  ## 将 db_shared_buffers 设置为总内存的最大 25%。
  ## 将根据检测到的 RAM 由 bootstrap 自动设置,您也可以覆盖此值
  db_shared_buffers: "128MB"

  ## 可能改善排序性能,但会增加每个连接的内存使用量
  #db_work_mem: "40MB"

  ## 此容器应使用哪个 Git 版本?(默认:tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

  ## 支持多少并发 Web 请求?取决于内存和 CPU 核心数。
  ## 将根据检测到的 CPU 由 bootstrap 自动设置,您也可以覆盖此值
  UNICORN_WORKERS: 2

  ## TODO: 此 Discourse 实例将响应的域名
  ## 必填项。Discourse 无法仅使用 IP 地址运行。
  DISCOURSE_HOSTNAME: community.mysite.com

  ## 如果希望容器以与上述指定的相同主机名(-h 选项)启动,请取消注释以下行(默认为 "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: 初始注册时将被设为管理员和开发者的逗号分隔邮箱列表
  ## 示例:'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'myemail'

  ## TODO: 用于验证新账户和发送通知的 SMTP 邮件服务器
  # 需要 SMTP 地址、用户名和密码
  # 警告:SMTP 密码中的字符 '#' 可能导致问题!
  DISCOURSE_SMTP_ADDRESS: smtp.mailgun.org
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: my-smtp-username
  DISCOURSE_SMTP_PASSWORD: "my-smtp-password"
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (可选,默认为 true)

  ## 如果您添加了 Let's Encrypt 模板,请取消注释以下行以获取免费 SSL 证书
  LETSENCRYPT_ACCOUNT_EMAIL: myemail

  ## 此 Discourse 实例的 http 或 https CDN 地址(配置为拉取)
  ## 请参阅 https://meta.discourse.org/t/14857 了解详情
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com

## Docker 容器是无状态的;所有数据都存储在 /shared 中
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## 插件请放在此处
## 请参阅 https://meta.discourse.org/t/19157 了解详情
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## 构建后运行的任何自定义命令
run:
  - exec: echo "开始自定义命令"
  ## 如果您想设置首次注册时的 'From' 邮箱地址,请取消注释并修改:
  ## 收到首次注册邮件后,请重新注释该行。它只需运行一次。
  #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"
  - exec: echo "结束自定义命令"

有人能告诉我我在哪里出错了,或者可能遗漏了什么吗?谢谢!

尝试使用 nginx 作为反向代理,成功率会高得多。

如果您的系统允许,请考虑使用 nginx 替代 apache2。

所以社区没有重定向到你的域名,而是指向你的 IP 地址。/etc/apache2/sites-enabled/ 目录中存在符号链接吗?

proxy_module 模块是否已加载?
apache2ctl -M

没有符号链接。

是的。幸运的是,这是我链接的教程中的步骤之一。

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_balancer
a2enmod lbmethod_byrequests

这一步

a2ensite community.yoursite.com

可能执行得不太顺利,可能需要加上 sudo。
也可以尝试执行以下命令:

sudo ln -s /etc/apache2/sites-available/community.yoursite.com.conf /etc/apache2/sites-enabled/

然后运行

sudo apachectl configtest

并祈祷一切顺利。
最后执行

sudo systemctl restart apache2

亲爱的 @OrbitStorm

我快速查看了你为 Discourse 配置的 Apache2 虚拟主机设置以及你的 yml 文件,发现它们似乎并未正确配置。

以下是一些提示:

首先,当你通过反向代理运行 Discourse 时,不应在 Discourse 的 yml 配置中启用 SSL LETSENCRYPT(请参见下方的工作示例)。Discourse 只需要一个端口与反向代理通信,且该连接无需 SSL 加密。

其次,查看你的主虚拟主机配置(即反向代理上的 443 端口):

<IfModule mod_ssl.c>
<VirtualHost *:443>
	ServerAdmin webmaster@localhost
	ServerName mysite.com
	ServerAlias www.mysite.com
	DocumentRoot /var/www/mysite.com
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

       Include /etc/letsencrypt/options-ssl-apache.conf
       SSLCertificateFile /etc/letsencrypt/live/mysite.com/fullchain.pem
       SSLCertificateKeyFile /etc/letsencrypt/live/mysite.com/privkey.pem
</VirtualHost>
</IfModule>

上述配置缺少所有必要的反向代理信息(请参阅下方附带的工作配置)。

以下是为你准备的工作配置,其基本结构与 meta 上各类教程中所述一致(本站其他帖子中已有详细文档,因此这里主要是重复 meta 上的其他文档):

<VirtualHost *:80>
        ServerName discourse.your-great-web-site.com
        ServerAdmin webmaster@localhost
        ProxyPreserveHost On
        ErrorLog ${APACHE_LOG_DIR}/discourse_errors.log
        CustomLog ${APACHE_LOG_DIR}/discourse.log combined
        ModPagespeed Off
        RewriteEngine on
        RewriteCond %{SERVER_NAME} =discourse.your-great-web-site.com
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

请注意,在 80 端口的配置中,主要必需的指令是 ServerNameRewriteEngine 以及将流量重定向到 443 端口的重写规则。

另外,如果你正在运行 Apache2 的 mod_pagespeed,建议将其禁用,因为我尚未成功让 mod_pagespeed 与 Discourse 配合工作(而且我也看不出有任何必要这样做)。

以下是执行“实际工作”的主配置文件:

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerName discourse.your-great-web-site.com
        ServerAdmin webmaster@localhost
        SSLProxyEngine on      # 仅在 Let's Encrypt 配置完成并正常工作后启用此项
  	    RewriteEngine on
  	    ProxyPreserveHost On
  	    ProxyRequests Off
  	    RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}
 	    RequestHeader set X-Real-IP expr=%{REMOTE_ADDR}

        #ProxyPass / http://127.0.0.1:8888/           # 我们不使用端口,而是使用 your-great-web-site 套接字
        #ProxyPassReverse / http://127.0.0.1:8888/    # 我们不使用端口,而是使用 your-great-web-site 套接字
        ProxyPass / your-great-web-site:/var/discourse/shared/socket-only/nginx.http.sock|http://localhost/
        ProxyPassReverse  / your-great-web-site:/var/discourse/shared/socket-only/nginx.http.sock|http://localhost/

        ErrorLog ${APACHE_LOG_DIR}/discourse_errors_ssl.log
        #CustomLog ${APACHE_LOG_DIR}/discourse_ssl.log combined   # 生产环境中已禁用访问日志

        ModPagespeed Off
        SSLCertificateFile /etc/letsencrypt/live/discourse.your-great-web-site.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/discourse.your-great-web-site.com/privkey.pem
        Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

我们所有的 Discourse 配置都使用 Unix 域套接字,因此你需要根据实际情况调整配置。

需要理解的核心要点(总结如下)是:应在 Discourse 构建(yml)配置中禁用 LETSENCRYPT,并且仅向 Discourse 暴露单一入口点。在我们的案例中是 Unix 域套接字,而在你的案例中应为单个 TCP/IP 套接字。

然后,从 443 端口的虚拟主机文件(而非 80 端口的虚拟主机)反向代理到该入口点。80 端口的虚拟主机仅负责重定向到 443 端口。你的 443 SSL 全部由反向代理中的 LETSENCRYPT 处理。Discourse 的 yml 文件中无需任何 SSL 设置(请参见下方的工作示例)。

以下是我们其中一个工作正常的 yml 文件(对应上述配置),供你参考:

/var/discourse/containers$ cat socket-only.yml
# 重要:为 Postgres 中的 Discourse 用户设置一个秘密密码
# TODO:在此模板中更改 SOME_SECRET

templates:
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
#  - "templates/sshd.template.yml"
## 如果希望添加 Let's Encrypt (https),请取消注释以下两行
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

## 此容器应暴露哪些 TCP/IP 端口?
## 如果你希望 Discourse 与 Apache 或 nginx 等其他 Web 服务器共享端口,
## 请参阅 https://meta.discourse.org/t/17247 了解详情
expose:
#  - "80:80"   # http
#  - "443:443" # https

# 使用 'links' 键将容器链接在一起,即使用 Docker --link 标志。
links:
  - link:
      name: data
      alias: data

# 是否有任何额外的 Docker 参数?
# docker_args:

params:
  ## 此容器应使用哪个 Git 修订版本?(默认:tests-passed)
  #version: latest
  db_shared_buffers: "4GB"

env:
  LC_ALL: en_US.UTF-8
  LANG: en_US.UTF-8
  LANGUAGE: en_US.UTF-8

  # DISCOURSE_DEFAULT_LOCALE: en

  ## 支持多少个并发 Web 请求?取决于内存和 CPU 核心数。
  ## 将基于检测到的 CPU 数量由 bootstrap 自动设置,你也可以覆盖
  #UNICORN_WORKERS: 3
  UNICORN_WORKERS: 8

  ## TODO:此 Discourse 实例将响应的域名
  DISCOURSE_HOSTNAME: 'discourse.your-great-web-site.com'

  ## 如果希望容器以与上述指定的相同主机名(-h 选项)启动,请取消注释
  #DOCKER_USE_HOSTNAME: true

  ## TODO:初始注册时将设为管理员和开发者的逗号分隔邮箱列表
  ## 示例:'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'tim@discourse.your-great-web-site.com'

  ## TODO:用于验证新账户和发送通知的 SMTP 邮件服务器
  # SMTP 地址、用户名和密码为必填项
  # 警告:SMTP 密码中的字符 '#' 可能导致问题!
  DISCOURSE_SMTP_ADDRESS: smtp.gmail.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: not_for_reply@discourse.your-great-web-site.com
  DISCOURSE_SMTP_PASSWORD: my_super_secret_cool_password
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (可选,默认为 true)

  ## 如果已添加 Let's Encrypt 模板,请取消注释以下行以获取免费 SSL 证书
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  ## TODO:配置与数据库的连接
  DISCOURSE_DB_SOCKET: ''
  #DISCOURSE_DB_USERNAME: discourse
  DISCOURSE_DB_PASSWORD: another_super_secret_cool_password
  DISCOURSE_DB_HOST: data
  DISCOURSE_REDIS_HOST: data

  DISCOURSE_MAXMIND_LICENSE_KEY: my_max_mind_key
  ## 此 Discourse 实例的 http 或 https CDN 地址(配置为拉取)
  ## 请参阅 https://meta.discourse.org/t/14857 了解详情
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com

volumes:
  - volume:
      host: /var/discourse/shared/socket-only
      guest: /shared
  - volume:
      host: /var/discourse/shared/socket-only/log/var-log
      guest: /var/log

## 插件请放在此处
## 请参阅 https://meta.discourse.org/t/19157 了解详情
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-bbcode.git
          - git clone https://github.com/discourse/discourse-sitemap.git
          - git clone https://github.com/discourse/discourse-solved.git
          - git clone https://github.com/discourse/discourse-whos-online.git
          - git clone https://github.com/unixneo/legacy-info.git

## 记住,这是 YAML 语法——你只能有一个带名称的块
run:
  - exec: echo "开始自定义命令"

  ## 如果希望为 root 配置密码登录,请取消注释并修改:
  ## 仅使用以下其中一行:
  #- exec: /usr/sbin/usermod -p 'PASSWORD_HASH' root
  #- exec: /usr/sbin/usermod -p "$(mkpasswd -m sha-256 'RAW_PASSWORD')" root

  ## 如果希望授权其他用户,请取消注释并修改:
  #- exec: ssh-import-id username
  #- exec: ssh-import-id anotherusername

  - exec: echo "自定义命令结束"
  #- exec: awk -F\# '{print $1;}' ~/.ssh/authorized_keys | awk 'BEGIN { print "此容器的授权 SSH 密钥:"; } NF>=2 {print $NF;}'

一旦理解了基础知识,这其实非常简单;而理解基础知识确实大有裨益 :slight_smile:

请记住,在我们的 Discourse 配置中,我们并不运行单个容器(实际上,我们很少以单容器模式运行),因此我们的 yml 文件无法在单容器(独立)配置中工作。我提供此文件供你参考,以帮助你了解在 Apache2 后方完整工作配置的样子。

我们在 Apache2 和 nginx 反向代理后方都运行 Discourse。事实上,出于多种原因,我们仅使用反向代理。其中一个原因是,我们可以利用反向代理来过滤恶意机器人等(这是完全不同的话题)。

在 Apache2 站点后方通过反向代理运行 Docker 化 Discourse(无论是一个站点还是一百个站点)并不困难;但理解基本概念确实会有所帮助。

希望提供的基本概念和工作示例配置文件能帮助你推进进度,让 Discourse 顺利上线运行。

此致…

@neounix 非常感谢您如此详尽的回复,但我遇到的大部分问题似乎都源于找不到合适的更新后的教程。这里的一些教程提到在 yml 文件中为 Discourse 启用 SSL,其中一份教程还说我不需要在 VirtualHost 文件中添加代理信息,因为 Let’s Encrypt 应该会自动处理。您的示例甚至与我的默认配置大相径庭,这让它们和试图将 CentOS 配置与 Ubuntu 混用一样令人困惑。

您说得完全对,理解基础知识确实大有裨益,但问题在于基础知识并没有被充分涵盖,而当它们被提及的时候,往往还是三年前的旧内容,且完全没有考虑到有人使用的是 Apache 而非 Nginx,或是 Ubuntu 而非 CentOS。值得一提的是,Discourse 是我使用 Docker 的唯一原因。

在花了四天时间仅仅尝试将应用程序与 Apache 一起安装后,我放弃了。无论是否免费,这都不值得如此费神,我也不打算继续在论坛上翻找,只为不断发现那些复制粘贴来的、指向同样两份过时不完整的教程的链接。在安装论坛软件方面,我从未有过像 Discourse 这样令人沮丧的经历,这足以说明一切。XenForo 和 Invision 是我拥有无限经验的两个平台,它们的安装和使用简直轻而易举。

我确实非常感激您为回复所付出的努力,但我不打算要求您替我完成这项工作,如果存在完善的文档,我也不应该被迫这样做。令我感到不可思议的是,像我这样并非那么特殊的特定情况,竟然没有在任何新教程中被强调,以便让新用户更容易完成这一过程。

无论如何,祝您一切顺利。

尽管我原本打算彻底放弃 Discourse,因为目前缺乏文档,且现有文档都过度聚焦于 CentOS/Nginx,但 DigitalOcean 上一位非常慷慨且耐心的用户回复了我在那里创建的帖子。经过一番尝试和错误后,他帮助整理了一份简单易懂的教程,专为像我这样处境的新 Discourse 用户准备。

作为回顾,这些情况包括:

在同一台服务器上安装 Discourse 和 Apache | 使用 Ubuntu 18.04 | DigitalOcean

感谢 Bobbyiliev @ DigitalOcean
https://www.digitalocean.com/community/questions/install-discourse-on-a-droplet-with-wordpress-served-by-apache(第一个回答)

前提条件

  • 为保险起见,请确保备份您的 Droplet,以便在出现问题时可以回滚到正常工作的版本
  • 通过 SSH 连接到您的 Droplet
  • 已安装 Apache,您可以按照以下步骤操作:

步骤 1 - 安装 Docker

请按照以下步骤安装 Docker:

步骤 2 - 下载 Discourse

首先,创建一个目录用于存储您的 Discourse 文件:

mkdir /var/discourse

然后,将官方的 Discourse Docker 镜像克隆到 /var/discourse 目录中。

git clone https://github.com/discourse/discourse_docker.git /var/discourse

步骤 3 - 配置 Discourse 监听 8080 端口

我们将使用 standalone.yml 模板,它包含了所有必要的服务,如 PostgreSQL、Redis 等。

使用以下命令复制示例文件:

cp /var/discourse/samples/standalone.yml /var/discourse/containers/app.yml

然后使用您喜欢的编辑器打开 /var/discourse/containers/app.yml 文件,并更新第 23 和 24 行的端口配置:

## 此容器应暴露哪些 TCP/IP 端口?
expose:
  - "8080:80"   # 将主机端口 8080 转发到容器端口 80(HTTP)
  - "8443:443"   # 将主机端口 8443 转发到容器端口 443(HTTPS)

如果您尚未拥有 SSL 证书,请确保注释掉第 16 行:

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

只需在 - "templates/web.ssl.template.yml 行前添加 # 符号,否则 Discourse 将无法启动。

步骤 4 - 设置 Discourse

切换目录:

cd /var/discourse

然后启动 Discourse(由于这是首次启动该服务,它将使用 app.yml 文件中的新配置引导应用程序):

./discourse-setup

注意:请确保提供有效的邮件服务器设置,否则设置可能会失败。

步骤 5 - 配置 Apache

在您的 /etc/apache2/sites-available/ 目录下创建一个名为 forum.example.com.conf 的新文件,并添加以下虚拟主机配置内容:

<VirtualHost *:80>
  ServerName forum.example.com
  ServerAlias www.forum.example.com

  <IfModule proxy_module>
    ProxyPreserveHost on
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
  </IfModule>
</VirtualHost>
  • 使用以下命令启用该虚拟主机:
a2ensite forum.example.com
  • 启用 Mod Proxy:
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_balancer
a2enmod lbmethod_byrequests
  • 重启 Apache:
systemctl restart apache2

完成上述步骤后,您就可以通过域名直接访问 Discourse 了。


注意 1:在安装 Discourse 时,app.yml 文件可能会被覆盖,导致第 16/17 行(SSL 相关)被取消注释。您需要再次注释掉这些行,然后重新构建应用(别忘了切换目录):./launcher rebuild app

注意 2:本指南中 Discourse 并未启用 SSL。不出所料,目前没有任何文档说明如何在 Apache 已启用 Let’s Encrypt SSL 的情况下为 Discourse 启用 SSL。如果有人找到解决方案,请联系我们。