【LLM】局域网内为容器服务启用HTTPS

OpenWebUI中,语音输入需要HTTPS才能使用麦克风等硬件资源,在局域网中通过NGINX转发实现HTTPS访问。

具体包含三个部分

  • 容器部署open-webui和nginx
  • 生成ssl证书
  • 修改nginx配置文件

1、容器部署

基于docker-compose,执行docker-compose up -d即可。

其中主要的就是映射conf文件夹和ssl的路径,不映射手动添加也一样。open-webui的环境变量就看个人需求了。

yaml 复制代码
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: always
    networks:
    - openwebui
  
  nginx:
    image: nginx
    container_name: nginx
    restart: always
    volumes:
    - /nginx/conf.d:/etc/nginx/conf.d
    - /nginx/ssl:/ssl
    ports:
    - "38080:8080"
    networks:
    - openwebui

# 为空即可
networks:
  openwebui:

2、生成ssl证书

这网上教程很多,基本一个命令就搞定,这里找GPT写了一个基于Flask的页面,方便访问(问就是因为懒得敲命令 )。具体代码放最后,有需要的可以复制。生成的文件放到上面配置的ssl文件夹里。

3.修改conf

conf.d文件夹下新建default.conf,写入内容如下,修改server_name以及ssl相关文件。

由于配置了network,localtion中访问地址可以直接用docker-compose中的app名字。

重启nginx容器即可访问。

shell 复制代码
server {
	listen 8080 ssl;
	server_name yourdomain.com;
	# 配置crt
	ssl_certificate "/ssl/xxx.crt";
	# 配置key
	ssl_certificate_key "/ssl/xxx.key";
	
	location /{
		proxy_pass http://open-webui:8080;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_set_header X-NginX-Proxy true;
		proxy_redirect default;
	}
}

附.生成ssl证书代码

pip install cryptography

python 复制代码
import os
from flask import Flask, jsonify, send_from_directory, request, render_template_string
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timedelta

app = Flask(__name__)

# SSL 证书文件夹
SSL_FOLDER = "ssl"
if not os.path.exists(SSL_FOLDER):
    os.makedirs(SSL_FOLDER)


# 生成 SSL 证书
def generate_ssl_cert(domain_name):
    key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )

    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
        x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Organization"),
        x509.NameAttribute(NameOID.COMMON_NAME, domain_name),
    ])

    cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.utcnow()
    ).not_valid_after(
        datetime.utcnow() + timedelta(days=365)
    ).add_extension(
        x509.SubjectAlternativeName([x509.DNSName(domain_name)]),
        critical=False,
    ).sign(key, hashes.SHA256(), default_backend())

    cert_filename = f"{domain_name}.crt"
    key_filename = f"{domain_name}.key"

    cert_path = os.path.join(SSL_FOLDER, cert_filename)
    key_path = os.path.join(SSL_FOLDER, key_filename)

    with open(key_path, "wb") as f:
        f.write(key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption()
        ))

    with open(cert_path, "wb") as f:
        f.write(cert.public_bytes(serialization.Encoding.PEM))

    return cert_filename, key_filename


# 首页接口,显示 HTML 页面
@app.route("/", methods=["GET"])
def index():
    return render_template_string('''
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSL Certificate Generator</title>
    <script>
        async function generateCert() {
            const domainName = document.getElementById('domain_name').value;
            const response = await fetch('/generate_cert', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({ domain_name: domainName })
            });

            if (response.ok) {
                const result = await response.json();
                document.getElementById('cert_link').href = `/download_cert/${result.certificate}`;
                document.getElementById('cert_link').innerText = result.certificate;
                document.getElementById('key_link').href = `/download_cert/${result.key}`;
                document.getElementById('key_link').innerText = result.key;
            } else {
                alert('Error generating certificate');
            }
        }
    </script>
</head>
<body>
    <h1>SSL Certificate Generator</h1>
    <form οnsubmit="event.preventDefault(); generateCert();">
        <label for="domain_name">Domain Name:</label>
        <input type="text" id="domain_name" name="domain_name" required>
        <button type="submit">Generate Certificate</button>
    </form>

    <h2>Generated Files</h2>
    <p>Certificate: <a id="cert_link" href="#" target="_blank">No certificate generated</a></p>
    <p>Private Key: <a id="key_link" href="#" target="_blank">No key generated</a></p>
</body>
</html>
    ''')


# 生成证书接口
@app.route("/generate_cert", methods=["POST"])
def generate_cert():
    domain_name = request.form.get("domain_name")
    if not domain_name:
        return jsonify({"error": "Domain name is required"}), 400

    cert_filename, key_filename = generate_ssl_cert(domain_name)
    return jsonify({"certificate": cert_filename, "key": key_filename})


# 下载证书接口
@app.route("/download_cert/<filename>", methods=["GET"])
def download_cert(filename):
    return send_from_directory(SSL_FOLDER, filename)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
相关推荐
十里清风3 小时前
LLM量化方法:ZeroQuant、LLM.int8()、SmoothQuant、GPTQ、AWQ
llm
知来者逆17 小时前
在与大语言模型交互中的礼貌现象:技术影响、社会行为与文化意义的多维度探讨
人工智能·深度学习·语言模型·自然语言处理·llm
SHIPKING3931 天前
【Prompt工程—文生图】案例大全
llm·prompt·文生图
水煮蛋不加蛋1 天前
AutoGen 框架解析:微软开源的多人 Agent 协作新范式
人工智能·microsoft·ai·开源·大模型·llm·agent
Two summers ago1 天前
arXiv2025 | TTRL: Test-Time Reinforcement Learning
论文阅读·人工智能·机器学习·llm·强化学习
AI大模型顾潇2 天前
[特殊字符] Milvus + LLM大模型:打造智能电影知识库系统
数据库·人工智能·机器学习·大模型·llm·llama·milvus
Alfred king4 天前
华为昇腾910B通过vllm部署InternVL3-8B教程
llm·nlp·vllm部署
CoderJia程序员甲4 天前
AI驱动的Kubernetes管理:kubectl-ai 如何简化你的云原生运维
运维·人工智能·云原生·kubernetes·llm
董厂长5 天前
LLM :Function Call、MCP协议与A2A协议
网络·人工智能·深度学习·llm
tangjunjun-owen5 天前
第三章:langchain加载word文档构建RAG检索教程(基于FAISS库为例)
langchain·llm·word·faiss·rag