nohup 和 setsid 都救不了:WSL2 里用 systemd 守护开发服务的正确姿势
nohup 和 setsid 都救不了:WSL2 里用 systemd 守护开发服务的正确姿势
我想在 WSL2 里把本地开发服务长期跑起来,这样随时能在 Windows 浏览器里预览站点。
听起来是个"加个 & 后台运行"就能搞定的事。结果连着失败了三次——而且前两次都是看起来成功了,过几分钟才发现进程早没了。
这篇文章记录完整的失败过程、journal 里的关键证据,以及最终跑通的配置。所有输出均来自真实环境。
第一次:nohup + disown
最直觉的做法:
wsl -d Ubuntu-22.04 -- bash -c '
cd ~/weiserv/site
nohup python3 manage.py runserver 0.0.0.0:8000 > runserver.log 2>&1 &
disown
sleep 7
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/
'
# 输出:200
成了?并没有。换一次独立的 wsl 调用再查:
runserver 进程数: 0
8000 无监听
日志里只留着两条请求记录,之后进程就随会话退出被回收了。
⚠️ 这是第一个坑:"在同一条命令里 curl 通了"是假信号。
进程是在那条命令的会话里活着的,会话一结束就没了。
必须在一次"独立的新调用"里复核,才算真的存活。
第二次:setsid
既然是会话退出带走了进程,那就彻底脱离会话:
setsid nohup python3 manage.py runserver 0.0.0.0:8000 \
> runserver.log 2>&1 < /dev/null &
当场 curl 依然 200。过几分钟再查 → 依旧连不上,进程已死。
setsid 在常规 Linux 上是有效的,但在 WSL2 这个场景下同样保不住。
第三次:systemd 服务,但只 start 不 enable
WSL2 的 Ubuntu 22.04 是可以跑 systemd 的(我的 MySQL 就靠它拉起来的)。于是写个服务:
# /etc/systemd/system/weiserv-dev.service
[Unit]
Description=weiserv Django dev server
After=network.target mysql.service
[Service]
Type=simple
User=<你的用户名>
WorkingDirectory=/home/<你的用户名>/weiserv/site
EnvironmentFile=/home/<你的用户名>/weiserv/.env
ExecStart=/usr/bin/python3 /home/<你的用户名>/weiserv/site/manage.py runserver 0.0.0.0:8002
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start weiserv-dev
systemctl is-active weiserv-dev
# active ← 看起来成了
大约 33 秒后,又没了。 这次我去翻了 journal,看到关键证据:
systemd[1]: Started weiserv Django dev server.
systemd[1]: Stopping weiserv Django dev server...
systemd[1]: weiserv-dev.service: Deactivated successfully.
systemd[1]: Stopped weiserv Django dev server.
注意是 Stopping(被主动停止),不是崩溃。
⚠️ 这是第二个坑,也是最容易误判的一个:
因为是"主动停"而非崩溃,所以Restart=always根本不会触发重启。
你会看到服务"优雅地消失",然后百思不得其解——明明配了自动重启啊。
最终方案:enable 才是关键
sudo systemctl enable --now weiserv-dev
is-active→activeis-enabled→enabled
再跨多次独立调用复核,服务持续存活,所有路由正常。
判据:systemctl is-enabled 必须是 enabled。
回头看就明白了——MySQL 之所以能长期活着,正是因为它本来就是 enabled 的。
顺带踩到的四个坑
坑 1:Django 命令不能用 root 跑
一开始我习惯性地加了 -u root:
wsl -d Ubuntu-22.04 -u root -- bash -c 'python3 manage.py check'
结果:
ImportError: Couldn't import Django. Are you sure it's installed and
available on your PYTHONPATH environment variable?
但明明 python3 -c "import django" 是好的。
原因:Django 是装在普通用户的 user site-packages 里的
(~/.local/lib/python3.10/site-packages),
root 的 PYTHONPATH 看不到它。
所以服务单元里必须写 User=<你的用户名>;手动执行时不要加 -u root。
坑 2:WSL2 里 sudo 会卡死
非交互场景下没有 TTY,sudo 会停在等密码输入,直到超时被 SIGTERM,而且没有任何输出——非常难排查。
需要 root 权限时(比如装服务、起 MySQL),直接用:
wsl -d Ubuntu-22.04 -u root -- <命令>
以 root 身份直接跑,绕开 sudo。
坑 3:http_proxy 会伪造故障信号
本机环境里配了代理(http_proxy=http://127.0.0.1:55989)。这时候 curl 本地服务:
curl http://localhost:8002/
# 502
502 是代理返回的,不是你的服务挂了。看着像"服务又死了",其实服务好好的。
排查本地服务时务必绕过代理:
curl --noproxy '*' http://localhost:8002/
# 200
浏览器访问 localhost 一般会自动绕过代理,所以用浏览器通常是正常的——这也是它容易被忽略的原因。
坑 4:端口冲突
8000、8001 已经被同在 WSL2 里的另一个项目占用了。所以我把开发服务固定在 8002,并且在 service 文件的注释里写明原因,避免以后自己手贱改回去。
# 端口说明:8000/8001 已被其它项目占用,故固定使用 8002
一份可以抄的完整配置
把服务定义放在项目里而不是只丢在 /etc(便于版本管理和换机器复用):
项目/ops/wsl2/weiserv-dev.service → scp/cp 到 /etc/systemd/system/
部署:
sudo cp /mnt/c/<项目>/ops/wsl2/weiserv-dev.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now weiserv-dev
日常:
systemctl status weiserv-dev
systemctl restart weiserv-dev
journalctl -u weiserv-dev -n 50 --no-pager
小结
| 方式 | 结果 | 原因 |
|---|---|---|
nohup ... & disown |
❌ 会话结束即死 | 进程随 WSL 会话被回收 |
setsid nohup ... & |
❌ 同样会死 | 同上,未能脱离回收 |
systemd start(不 enable) |
❌ 约 30 秒后被停 | 被主动 Stopping,Restart=always 不触发 |
systemd enable --now |
✅ 长期存活 | 与 MySQL 同生命周期 |
三条经验:
- "当场 curl 200"不算数,必须在独立的新调用里复核;
- 服务消失先翻
journalctl,分清是崩溃还是被主动停——二者对策完全不同; - WSL2 里要长期跑东西,走 systemd 并
enable,别再和nohup较劲了。
系列阅读
- WSL2 Ubuntu 22.04 安装指南——本文的 WSL2 环境从哪来:手动安装踩坑实录。
- WSL2 离线安装资源与 SHA256 校验——没网/网慢的环境怎么装:官方介质直链、校验值与离线导入实测。
本文环境:Windows 11 + WSL2 Ubuntu-22.04(systemd 生效)/Python 3.10.12/Django 4.2.30/MySQL 8.0.46
文中命令、journal 输出与失败过程均为真实环境实测记录。