启动Flask/FastAPI HTTPS时证书路径须为绝对路径且可读,格式为PEM;私钥不可加密、权限需严格;HTTP→HTTPS跳转应在服务器层实现;localhost自签名证书必被浏览器警告;生产环境应使用Nginx反代而非Python直连SSL。

用 ssl_context 启动 Flask/FastAPI 时证书路径必须绝对且可读
本地跑 HTTPS 不是把 PEM 文件丢进项目目录就完事——Web 框架启动时会以当前工作目录为基准解析路径,而 IDE、systemd、docker 的工作目录往往不一致,导致 FileNotFoundError 或静默降级为 HTTP。
- 始终用
os.path.abspath()或pathlib.Path(__file__).parent / "cert.pem"构造绝对路径 - 证书和私钥必须是 PEM 格式;若用 OpenSSL 生成,确认私钥未加密(
openssl rsa -in key.enc.pem -out key.pem) - Flask 示例:
app.run(ssl_context=("/abs/path/cert.pem", "/abs/path/key.pem"));FastAPI 的uvicorn.run(..., ssl_keyfile=..., ssl_certfile=...)同理 - Linux 下注意文件权限:Web 进程用户(如
www-data)需有读取私钥的权限,但不能是 world-readable(否则 uvicorn 会拒绝加载)
HTTP 到 HTTPS 强制跳转不能只靠前端重定向
浏览器地址栏输 http://localhost:5000 就走 HTTP,前端 JS 无法拦截或改写这个初始请求。真跳转必须发生在 Web 服务器层或反向代理层。
- 纯 Python 启动时,最简方案是开两个服务:一个 HTTP 服务监听 80 端口,响应 301 到
https://$host$request_uri;另一个 HTTPS 服务监听 443 - Flask 中可用
before_request检查request.url.startswith("http://")并redirect(..., code=301),但仅对非静态资源有效;CSS/JS 请求可能被浏览器阻止混合内容 - 更可靠的做法是用 Nginx 做反代:
return 301 https://$host$request_uri;放在http {}块的 server 监听 80 端口配置里 - 开发阶段可直接禁用 HTTP:只启 HTTPS 端口,让浏览器报错——比“看似能用实则不安全”的混合模式更早暴露问题
localhost 用自签名证书会被浏览器标记“不安全”,但可绕过
浏览器对 localhost 不豁免证书校验,自签名证书必然触发 NET::ERR_CERT_AUTHORITY_INVALID。这不是配置错,是设计如此。
- Chrome/Firefox 可临时输入
thisisunsafe(无空格、无回车)跳过警告页;Edge 是badidea - Mac 上可通过
security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem把证书加进系统信任库(需重启浏览器) - 不要试图用 OpenSSL 生成“看起来像 Let's Encrypt”的证书——浏览器校验证书链时仍会失败;Let's Encrypt 不签
localhost - 若用
127.0.0.1替代localhost,部分旧版 Chrome 会略过警告,但新版已统一处理,不建议依赖
生产环境别用 Python 自带 SSL 上 HTTPS
Python 的 ssl_context 参数本质是调用 OpenSSL 库做 TLS 握手,它不支持 ALPN、OCSP stapling、TLS 1.3 完整特性,也没连接复用、HTTP/2 支持,性能和兼容性远不如成熟反向代理。
立即学习“Python免费学习笔记(深入)”;
- Uvicorn + Nginx 是事实标准:Nginx 处理 TLS 终止、证书管理、HTTP/2、gzip、缓存;Uvicorn 专注业务逻辑
- Let's Encrypt 证书自动续期必须由 Nginx 或单独进程(如
certbot)完成,Python 进程无法感知磁盘证书更新 - 若坚持用 Python 直接 HTTPS,记得设
ssl_context="adhoc"仅用于调试——它每次启动动态生成证书,浏览器会反复报错,且不支持 SNI










