Run Python Apps With systemd on Ubuntu: Service Files, Logs and Restart Policies is a practical walkthrough for engineers who want to understand the reasoning behind a working setup—not only paste commands. Examples assume an authorized lab or server. Verify the operating-system version, back up important data and read commands before using them on production systems.
The practical problem
A script that works in a shell can fail the moment you disconnect from SSH. For background APIs, queue consumers and internal workers, systemd offers startup ordering, automatic restarts, logs and resource controls without a hand-maintained terminal. We’ll deploy a small Python worker called inventory-sync and examine the details that prevent hard-to-debug incidents. This tutorial assumes a Linux distribution running systemd and a script you own; the sample paths and usernames are illustrative.
Prepare the host and plan a safe change
Create a dedicated non-login account, such as sudo useradd --system --home /opt/inventory-sync --shell /usr/sbin/nologin inventorysync, then create a project directory owned by the service operator. Put your app code in a controlled release path and prepare a Python virtual environment. Install locked dependencies, not unreviewed packages as root. Confirm /opt/inventory-sync/.venv/bin/python /opt/inventory-sync/worker.py runs under the intended service account before creating a unit.
Installation and configuration example
A minimal /etc/systemd/system/inventory-sync.service can use [Unit], Description=Inventory sync worker, After=network-online.target; then [Service], Type=simple, User=inventorysync, WorkingDirectory=/opt/inventory-sync, ExecStart=/opt/inventory-sync/.venv/bin/python /opt/inventory-sync/worker.py, Restart=on-failure, RestartSec=5; and [Install], WantedBy=multi-user.target. Write each directive on its own line in the actual unit, not as one prose string. Run sudo systemd-analyze verify /etc/systemd/system/inventory-sync.service and sudo systemctl daemon-reload.
Verify the result before declaring success
Start with sudo systemctl enable --now inventory-sync, then inspect systemctl status inventory-sync and journalctl -u inventory-sync -n 80 --no-pager. For a web app that binds a network port, your ExecStart may instead call a production application server such as Uvicorn, with an explicitly configured host address. Avoid appending & or wrapping a service in shell backgrounding: systemd needs to supervise the actual foreground process. When deployments update a unit file, reload the manager; when code changes, restart the service with an operational plan.
Troubleshooting and rollback
A surprising failure mode is status=203/EXEC, often caused by an invalid executable path or permissions. Other issues include imports working in your shell but not in the service’s environment, inability to write to a directory, or a crash loop hidden by aggressive restart settings. Inspect journal messages and confirm the account can read every parent directory. Use systemctl show inventory-sync -p User -p ExecStart to confirm the effective configuration. If a worker is expected to exit successfully on a schedule, consider a timer instead of a perpetually restarting daemon.
Make it dependable in production
Production units should use NoNewPrivileges=true and filesystem protections where compatible with app requirements, sensible memory and CPU limits, and credentials from an approved secret store or protected environment file. Do not put plaintext API tokens directly into units committed to Git. Define health criteria beyond “process exists”: a worker can be running while making no progress. Create metrics or heartbeat events, monitor restarts, and document rollback to a known-good release. Keep the service user separated from privileged administration.
Operational example: from a lab change to a managed service
Imagine the same server moving from an individual’s test environment into a small team’s production fleet. The command that proved the idea still matters, but it is no longer the whole delivery plan. Assign a specific owner, record the intended configuration in version control, create a change ticket or deployment record, and test the behavior in staging before rolling it to every machine. Keep the old configuration or previous release available so you can restore service quickly if assumptions prove wrong. Most Linux incidents are not caused by one mysterious command; they grow from undocumented changes and missing verification.
For this topic, build a compact runbook with four things: the expected healthy state, the fastest read-only observation command, a low-risk diagnostic path, and an explicit rollback trigger. Pair logs with actual user-facing checks. If the main service says “active” but requests fail, the monitoring check should reveal that mismatch. A beginner can follow the commands in this guide to learn the mechanics, while an on-call engineer should be able to follow the runbook without guessing which changes are allowed during an outage.
Security and maintenance checklist
Apply least privilege, restrict unnecessary network exposure, and keep configuration and credentials separate. Review distribution security updates rather than freezing package versions forever; however, test major upgrades that can alter configuration defaults. Backups must include the data and metadata necessary to recreate a working service, and the restore procedure must be exercised. Record where logs appear and how to correlate them with incidents. Document any example-specific assumptions, such as a local hostname or account name, before colleagues reuse the steps. These operational details are what separate a tutorial that runs once from a deployment that survives the next six months.
FAQ
Can I run these commands on a production server directly?
Use a test host first and review the exact distribution and version. Commands that alter authentication, networking or filesystems can be disruptive. Confirm backup and console or rollback access before changing remote infrastructure.
How can I tell whether the setup is actually reliable?
Verify the expected behavior from another process or host, inspect relevant logs, and test a deliberate failure with a documented recovery procedure. A successful exit code from an installation command is only the beginning of validation.
Official documentation and related learning
Review the official reference for this component before deploying, particularly for version-dependent options. For a broader engineering perspective, explore ProdCraft’s self-hosted model operations guide and our security architecture checklist; the same principles of least privilege, observability and measured changes apply to AI infrastructure as well as traditional servers.
Hands-on exercise: prove the restart policy works
Use a disposable worker that logs its startup time, waits briefly, then exits with status 1. With a service configured as Restart=on-failure, watch journalctl -fu inventory-sync and observe the configured restart interval. Now change the worker to exit successfully and verify that it no longer restarts under that policy. This distinction helps when deciding whether a job belongs in a persistent service or a scheduled systemd timer. Avoid testing forced crashes on a live production queue consumer, as repeated starts may replay in-flight work.
For a production API, add a functional endpoint and an external health check rather than treating systemctl is-active as sufficient evidence. Pair this service guide with our HTTPS reverse proxy to learn how to expose a local service safely.



