{"id":1534,"date":"2026-10-28T22:04:49","date_gmt":"2026-10-28T22:04:49","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1534"},"modified":"2026-10-28T22:04:49","modified_gmt":"2026-10-28T22:04:49","slug":"linux-vps-supervisor-python-worker-supervision","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/","title":{"rendered":"Supervising Python App Workers on a Linux VPS with Supervisor"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A web application started with <code>nohup python app.py &amp;<\/code> is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs to a file that grows without limit, and it keeps running after you deploy new code, which makes debugging a memory dump of a process that disagrees with the repository. On a small instance where every megabyte counts, that ten-line launch command is usually the weakest part of the stack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">systemd can supervise a process correctly, and it should be the default on any modern Linux host. Supervisor is the other common answer, and it remains the better fit when you need to run several instances of the same program with per-instance environment variables, or when the application runs as a non-root user inside an environment where writing system unit files is awkward. This walkthrough configures it properly, with pooling, restart backoff and log rotation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Decide Between Supervisor and systemd First<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both tools do the same core job: start a program, watch it, and restart it when it exits. systemd is already installed, integrates with <code>journalctl<\/code>, and supports socket activation and cgroup resource limits without extra software.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Supervisor earns its place when you want one config file to define a pool of identical workers that share a single <code>supervisorctl restart all<\/code> command, or when the deployment is inside a container where PID 1 is already occupied. If you are running a containerised stack, the process supervision model is different again \u2014 <a href=\"https:\/\/virtualserversvps.com\/vps-vs-shared-hosting.html\">how a VPS compares to shared hosting for running long-lived daemons<\/a> is worth reading before you commit to a pattern you will not be able to change later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Install and Lay Out the Config Directory<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The packaged version is current enough on Debian 12 and Ubuntu 24.04. Install it from the distro repositories rather than pip: the pip-installed copy is tied to a Python environment and disappears when that environment is rebuilt.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>sudo apt install -y supervisor\nsudo systemctl enable --now supervisor\nsupervisord --version        # 4.x is fine\n\nls -l \/etc\/supervisor\/conf.d\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep every program in its own file under <code>conf.d<\/code>. Editing the main <code>supervisord.conf<\/code> for each application turns it into an un-reviewable pile, and the include directive already points at <code>conf.d\/*.conf<\/code> by default.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two settings in the <code>[unix_http_server]<\/code> and <code>[supervisorctl]<\/code> sections are worth checking before you rely on remote tooling: the socket path and its permissions. On a multi-user host, a world-readable control socket lets any local user restart your application. Set <code>chmod=0700<\/code> and keep the socket under <code>\/run<\/code> so it is cleared on reboot rather than left stale on disk.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Write a Program Block That Restarts Correctly<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The restart behaviour is controlled by <code>autorestart<\/code> and <code>startretries<\/code>. Setting <code>autorestart=true<\/code> without a delay means a program with a syntax error restarts hundreds of times per minute and fills the disk with tracebacks. <code>startsecs<\/code> is the settling window: the process must stay alive that long before it counts as started.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>[program:api]\ncommand=\/opt\/app\/venv\/bin\/gunicorn -c \/opt\/app\/gunicorn.conf.py app.wsgi:application\ndirectory=\/opt\/app\nuser=app\nenvironment=PATH=\"\/opt\/app\/venv\/bin\",DJANGO_SETTINGS_MODULE=\"app.settings\"\nautostart=true\nautorestart=true\nstartsecs=5\nstartretries=3\nstopasgroup=true\nkillasgroup=true\nstopsignal=TERM\nstopwaitsecs=15\nstdout_logfile=\/var\/log\/supervisor\/api.out.log\nstderr_logfile=\/var\/log\/supervisor\/api.err.log\nstdout_logfile_maxbytes=20MB\nstdout_logfile_backups=3<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The two group settings matter more than they look. Without <code>stopasgroup<\/code> and <code>killasgroup<\/code>, a Gunicorn master that forks workers leaves orphaned children holding the listening socket after a stop, and the next start fails with &#8220;address already in use&#8221;. The <code>environment<\/code> line replaces rather than appends to the inherited environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Run a Pool of Instances Without Duplicating Files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Supervisor supports numeric ranges in program names, which creates independent processes from one block. Use this for CPU-bound workers, or for anything that must run one process per core. Stick to sockets or a queue instead if the processes need to share state.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>[program:worker]\ncommand=\/opt\/app\/venv\/bin\/python -m app.queue_worker\nprocess_name=%(program_name)s_%(process_num)02d\nnumprocs=2\nnumprocs_start=0\n...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two processes on a 2 vCPU instance is usually the ceiling; adding more increases context switching without increasing throughput. Verify with <code>supervisorctl status<\/code> that each instance shows RUNNING, not BACKOFF, and that PIDs differ.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Memory is the real constraint, not CPU count. Each Python worker with a loaded framework typically sits between 80 and 200 MB resident, so two workers can consume a third of a 1 GB instance before your web tier starts. Check with <code>ps_mem -p<\/code> after a few hours of traffic, when lazily imported modules are resident, rather than ten minutes after start.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handle Log Rotation Without Losing Lines<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Supervisor&#8217;s own <code>stdout_logfile_maxbytes<\/code> rotation truncates and reopens the file by moving it, which some applications handle badly if they keep their own file descriptor. If the application writes its own logs, use <code>logrotate<\/code> with <code>copytruncate<\/code> instead, and set supervisor&#8217;s own logging to only capture stderr.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/var\/log\/supervisor\/api.err.log {\n    daily\n    rotate 7\n    compress\n    missingok\n    notifempty\n    copytruncate\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Watch for the classic double-rotation problem: if both supervisor and logrotate act on the same file, you get an empty <code>.1<\/code> and a missing day of history. Pick one owner per file and document it in a comment at the top of the logrotate stanza.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The alternative is to send application output to the journal with <code>redirect_stderr=true<\/code>, letting journald handle retention. That is simpler, but it mixes application output into system logs and makes per-program inspection slower under load. For anything beyond a trickle of output, file-per-program plus logrotate remains easier to reason about.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reload Changes Without Dropping Requests<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>supervisorctl reread<\/code> loads changed config files without acting on them; <code>supervisorctl update<\/code> applies the changes to only the affected programs. That two-step is safer than <code>restart all<\/code>, which bounces every unrelated service on the host.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>sudo supervisorctl reread\nsudo supervisorctl update\nsudo supervisorctl status\n\n# targeted reload after a code deploy\nsudo supervisorctl restart api<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the program uses a graceful signal, prefer sending it directly rather than restarting, so in-flight requests complete. Gunicorn&#8217;s master handles <code>SIGHUP<\/code> by re-execing workers one at a time \u2014 that is the zero-downtime deploy path on a single instance.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Diagnose BACKOFF, FATAL and Silent Exits<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When a program will not stay up, <code>supervisorctl status<\/code> reports BACKOFF and stops retrying after <code>startretries<\/code>. The reason is in the program&#8217;s own stderr log, not in supervisor&#8217;s main log, which is the first place people look and the last place that has useful information.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a process that exits cleanly and is still restarted, check <code>exitcodes<\/code>: by default only 0 is considered successful, so a worker that exits 3 on &#8220;queue empty&#8221; will be restarted forever. Set <code>exitcodes=0,3<\/code> and let the worker finish. Once the service stays up under load, the remaining latency work is application-side; <a href=\"https:\/\/virtualserversvps.com\/managed-vs-unmanaged.html\">understanding what a managed plan does and does not cover<\/a> helps decide whether tuning this layer is your job or the provider&#8217;s.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>A web application started with nohup python app.py &amp; is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs to&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":1,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1534","post","type-post","status-publish","format-standard","hentry","category-vps-guides-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Supervising Python App Workers on a Linux VPS with Supervisor - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"A web application started with nohup python app.py &amp; is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Supervising Python App Workers on a Linux VPS with Supervisor\" \/>\n<meta property=\"og:description\" content=\"Supervising Python App Workers on a Linux VPS with Supervisor\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-28T22:04:49+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/\",\"name\":\"Supervising Python App Workers on a Linux VPS with Supervisor - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-10-28T22:04:49+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"A web application started with nohup python app.py &amp; is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Supervising Python App Workers on a Linux VPS with Supervisor\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Supervising Python App Workers on a Linux VPS with Supervisor - Virtual Servers VPS Blog","description":"A web application started with nohup python app.py &amp; is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/","og_locale":"en_US","og_type":"article","og_title":"Supervising Python App Workers on a Linux VPS with Supervisor","og_description":"Supervising Python App Workers on a Linux VPS with Supervisor","og_url":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-10-28T22:04:49+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/","url":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/","name":"Supervising Python App Workers on a Linux VPS with Supervisor - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-10-28T22:04:49+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"A web application started with nohup python app.py &amp; is not a service. It does not restart after a crash, it does not survive a reboot, it writes logs","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/linux-vps-supervisor-python-worker-supervision\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Supervising Python App Workers on a Linux VPS with Supervisor"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1534","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1534"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1534\/revisions"}],"predecessor-version":[{"id":1535,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1534\/revisions\/1535"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1534"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1534"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1534"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}