W weiserv
← 返回博客

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-activeactive
  • is-enabledenabled

再跨多次独立调用复核,服务持续存活,所有路由正常。

判据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 秒后被停 主动 StoppingRestart=always 不触发
systemd enable --now ✅ 长期存活 与 MySQL 同生命周期

三条经验

  1. "当场 curl 200"不算数,必须在独立的新调用里复核;
  2. 服务消失先翻 journalctl分清是崩溃还是被主动停——二者对策完全不同;
  3. WSL2 里要长期跑东西,走 systemd 并 enable,别再和 nohup 较劲了。

系列阅读


本文环境:Windows 11 + WSL2 Ubuntu-22.04(systemd 生效)/Python 3.10.12/Django 4.2.30/MySQL 8.0.46
文中命令、journal 输出与失败过程均为真实环境实测记录。

广告位占位 · post-inline