systemd管理Python服务启动后立即退出,主因是Type类型配置错误;应优先用Type=exec,避免脚本后台化,并配Restart=on-failure;环境变量需用Environment显式声明,日志应交由journal接管,权限问题需通过RuntimeDirectory、User、CapabilityBoundingSet等精确控制。

用 systemd 管理 Python 脚本服务时,为什么启动后立即退出?
常见原因是没正确设置 Type= 类型。Python 启动脚本默认是前台运行,但 systemd 默认按 Type=simple 等待主进程 fork 后立刻返回 —— 如果你的脚本没 daemonize,systemd 会认为它崩溃了,立刻杀掉并反复重启。
解决方法:优先用 Type=exec(适合不 fork 的前台常驻程序),或确保脚本本身不后台化、不调用 os.fork() / daemonize;若必须后台运行,改用 Type=forking 并严格提供 PIDFile=。
-
Type=exec最安全,适用于uvicorn、gunicorn --preload、或自己写的while True:循环服务 - 避免在 Python 脚本里调用
python-daemon或手动os.setsid()—— 和 systemd 冲突 - 务必加
Restart=on-failure和RestartSec=5,否则崩溃后服务就挂死
Python 进程找不到虚拟环境里的包,怎么让 systemd 正确加载 PATH 和环境变量?
systemd 服务运行在干净的最小环境中,~/.bashrc 或 source venv/bin/activate 全无效。不能靠 shell 包装器间接启动(比如写个 start.sh),那会让主进程变成 shell,systemd 无法准确追踪 Python。
正确做法:用 Environment= 显式声明所有依赖项,尤其是 PYTHONPATH 和 PATH:
立即学习“Python免费学习笔记(深入)”;
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
Environment="PATH=/opt/myapp/venv/bin:/usr/local/bin:/usr/bin:/bin" Environment="PYTHONPATH=/opt/myapp/src" Environment="DJANGO_SETTINGS_MODULE=myapp.settings.prod"
- 绝对路径必须写全,
~和$HOME在 service 文件里不展开 - 不要用
ExecStart=/opt/myapp/venv/bin/python app.py—— 改成ExecStart=/opt/myapp/venv/bin/gunicorn --chdir /opt/myapp/src myapp.wsgi:application,直接调用虚拟环境里的可执行文件 - 敏感变量(如密钥)建议用
EnvironmentFile=/etc/myapp/secrets.env单独管理,文件权限设为600
日志打不出来或轮转混乱,systemd journal 和 Python logging 怎么协同?
Python 默认把日志输出到 sys.stderr,systemd 会自动捕获,但如果你在代码里重定向了 stdout/stderr(比如写入文件、用 RotatingFileHandler),journal 就收不到任何内容,排查时一片空白。
生产建议:关闭 Python 自带的文件日志,完全交给 systemd journal 管理:
- 删掉或注释掉
FileHandler相关代码,只保留StreamHandler(sys.stdout) - 在 service 文件中加
StandardOutput=journal和StandardError=journal(默认就是,但显式写出来更稳) - 用
journalctl -u myapp.service -f实时看日志;加--since "2 hours ago"查历史 - 如果真需要落地文件,用
SystemMaxUse=和MaxFileSec=1day在/etc/systemd/journald.conf控制 journal 本身轮转,别让 Python 插手
部署后权限被拒、无法绑定 80 端口或访问数据库 socket,哪些路径权限容易被忽略?
systemd 服务默认以 root 启动,但你不该让 Python 进程长期以 root 运行。用 User= 和 Group= 降权后,常见失败点不是代码逻辑,而是路径不可达:
-
/run/myapp/(socket、pidfile 目录)必须由 service 文件中的RuntimeDirectory=myapp声明,并确保User=www-data对该目录有读写权 - SQLite 数据库文件所在目录需对服务用户可写,不能只给文件本身
chmod 644—— 目录至少要755,且属主匹配 - 绑定
:80需CapabilityBoundingSet=CAP_NET_BIND_SERVICE+AmbientCapabilities=CAP_NET_BIND_SERVICE,比改setcap更可控 - 用
ProtectSystem=strict和ProtectHome=true前先确认脚本是否读写了/tmp或/var/tmp—— 它们会被封锁,得显式加TemporaryFileSystem=/tmp
最常漏的是 RuntimeDirectoryMode=0755 和 UMask=0022,导致子进程创建的 pidfile 或 socket 权限不对,后续其他组件连不上。

















