You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Nginx实现类似Slack/GitLab的动态子域名功能

实现类似Slack/GitLab的子域名多租户功能(Nginx+代码方案)

我之前帮不少团队落地过类似的子域名多租户方案,刚好能给你捋清楚整个流程——核心就是通配符路由+租户识别的组合,下面拆解原理和具体实现步骤:

核心原理拆解

这类子域名机制本质分三步走:

  1. 通配符DNS:把所有*.abc.com格式的子域名都指向你的服务器集群,确保任何自定义子域名都能被解析到
  2. Nginx反向代理:统一接收所有子域名的请求,转发给后端服务的同时传递关键标识
  3. 后端租户匹配:解析子域名对应的组织/租户ID,从数据库拉取专属配置并返回对应内容

具体实现步骤

1. 先搞定DNS配置

首先要让所有子域名都能找到你的服务器:
去你的域名服务商后台,添加一条通配符A记录:

  • 主机记录:填*
  • 记录值:你的服务器公网IP(或负载均衡域名)

这样不管用户访问www.cop.abc.com还是www.myteam.abc.com,都会指向你的服务器。

2. Nginx配置:统一接收子域名请求

Nginx是整个流程的入口,这里提供两种常见配置方案:

方案1:全量转发子域名请求到后端

适合不想在Nginx层做复杂解析,把所有判断逻辑交给后端的场景:

server {
    listen 80;
    listen 443 ssl;
    server_name *.abc.com;

    # HTTPS必备:通配符SSL证书(可以去Let's Encrypt申请免费的)
    ssl_certificate /path/to/wildcard.abc.com.crt;
    ssl_certificate_key /path/to/wildcard.abc.com.key;

    # 转发请求到后端服务(比如Flask/Django/Node.js)
    location / {
        proxy_pass http://your-backend:8000;
        proxy_set_header Host $host; # 把原始请求的Host头传给后端
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 单独处理主域名(www.abc.com),避免和子域名逻辑混淆
    server_name www.abc.com;
    location / {
        proxy_pass http://your-backend:8000/main; # 主域名跳转专属路由
    }
}

方案2:Nginx提前提取子域名传给后端

如果想减少后端的解析工作量,可以用正则匹配直接提取组织名,通过自定义请求头传递:

server {
    listen 80;
    # 正则匹配www.xxx.abc.com格式,提取xxx作为组织名
    server_name ~^www\.(?<org_name>[a-zA-Z0-9-]+)\.abc\.com$;

    location / {
        proxy_pass http://your-backend:8000;
        proxy_set_header X-Org-Name $org_name; # 把提取到的组织名传给后端
        proxy_set_header Host $host;
    }
}

3. 后端代码逻辑:识别子域名并匹配组织

以Python Flask为例,展示两种处理方式:

方式1:从Host头解析子域名

from flask import Flask, request, abort
import re

app = Flask(__name__)

# 模拟数据库存储组织信息(实际项目用真实数据库)
org_database = {
    "cop": {"full_name": "Cop技术团队", "owner": "张三"},
    "myteam": {"full_name": "我的研发小组", "owner": "李四"}
}

def extract_org_from_host(host):
    # 匹配www.xxx.abc.com的子域名格式
    match_result = re.match(r"www\.([a-zA-Z0-9-]+)\.abc\.com$", host)
    if match_result:
        return match_result.group(1)
    return None

@app.route("/")
def org_homepage():
    current_host = request.host
    org_name = extract_org_from_host(current_host)

    if not org_name:
        abort(404) # 不是合法子域名,返回404

    org_info = org_database.get(org_name)
    if not org_info:
        return f"组织 {org_name} 不存在,请先注册"

    return f"欢迎访问 {org_info['full_name']} 的专属页面!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

方式2:从Nginx自定义头获取组织名

如果用了方案2的Nginx配置,后端直接读取自定义头即可,更高效:

@app.route("/")
def org_homepage():
    org_name = request.headers.get("X-Org-Name")
    if not org_name:
        abort(404)
    
    org_info = org_database.get(org_name)
    # 后续逻辑同上

4. 数据库设计关键要点

为了避免子域名冲突,组织表必须加唯一约束:

CREATE TABLE organizations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    org_name VARCHAR(50) NOT NULL UNIQUE, -- 子域名标识(比如cop、myteam)
    full_name VARCHAR(100) NOT NULL, -- 组织全名
    owner_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY (org_name)
);

用户创建组织时,要先校验org_name的合法性:

  • 只能包含字母、数字、连字符
  • 不能以连字符开头/结尾
  • 不能使用敏感词(比如admin、www、mail)
  • 检查是否已存在于数据库中

5. 额外注意事项

  • HTTPS证书:必须使用通配符SSL证书(*.abc.com),否则子域名会出现证书错误
  • 主域名隔离:确保主域名(www.abc.com)的逻辑和子域名完全分开,避免路由冲突
  • 缓存优化:高并发场景下,可以用Redis缓存组织信息,减少数据库查询压力
  • 子域名回收:如果组织注销,要及时清理数据库中的对应记录,避免后续被其他用户占用

内容的提问来源于stack exchange,提问作者cop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:25:43