<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Wick</title>
    <description>The latest articles on DEV Community by John Wick (@john_wick_47).</description>
    <link>https://dev.to/john_wick_47</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043176%2F1419fa33-a7c6-4395-aac9-ade52bb68338.jpg</url>
      <title>DEV Community: John Wick</title>
      <link>https://dev.to/john_wick_47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/john_wick_47"/>
    <language>en</language>
    <item>
      <title>Run Python Bots Without Sleep On Render With StayPresent</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:54:34 +0000</pubDate>
      <link>https://dev.to/john_wick_47/run-python-bots-without-sleep-on-render-with-staypresent-j5j</link>
      <guid>https://dev.to/john_wick_47/run-python-bots-without-sleep-on-render-with-staypresent-j5j</guid>
      <description>&lt;h1&gt;
  
  
  Deploy Python Bots on Render Without Sleep Using StayPresent
&lt;/h1&gt;

&lt;p&gt;Render is one of the most popular free hosting platforms for small Python projects, but it comes with a well-known catch: free-tier web services spin down after a period of inactivity and require a fresh incoming request to wake back up. For a &lt;strong&gt;render python bot&lt;/strong&gt; — a Discord bot, Telegram bot, or scraper — that "wake up" delay can mean minutes of downtime every time the service goes idle.&lt;/p&gt;

&lt;p&gt;This guide covers exactly how Render's sleep behavior works, and how to eliminate it using &lt;strong&gt;StayPresent&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Understanding Render's Free Tier&lt;/li&gt;
&lt;li&gt;Why HTTP Port Requirements Matter&lt;/li&gt;
&lt;li&gt;Setting Up StayPresent for Render&lt;/li&gt;
&lt;li&gt;Health Checks Explained&lt;/li&gt;
&lt;li&gt;Self-Ping to Avoid Idle Sleep&lt;/li&gt;
&lt;li&gt;Crash Recovery on Render&lt;/li&gt;
&lt;li&gt;Full Working Example&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Understanding Render's Free Tier
&lt;/h2&gt;

&lt;p&gt;Render's free web services sleep after roughly 15 minutes without incoming HTTP traffic. Once asleep, the next request has to spin the container back up before it responds, so real users (or a bot's own polling loop) experience a delay. Render also expects your web service to bind to a port it provides via the &lt;code&gt;PORT&lt;/code&gt; environment variable — if nothing is listening there, Render's own health checks will consider the deploy unhealthy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Render Health Check] --HTTP GET--&amp;gt; [Your Service on $PORT]
                                        |
                                No response = unhealthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why HTTP Port Requirements Matter
&lt;/h2&gt;

&lt;p&gt;A typical Telegram or Discord bot doesn't open an HTTP port — it just connects outward to Telegram's or Discord's API and waits for events. That's perfectly normal bot behavior, but it fails Render's expectations for a web service. The fix isn't to change how your bot works; it's to run a small HTTP server &lt;em&gt;next to&lt;/em&gt; it, purely so Render has something to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up StayPresent for Render
&lt;/h2&gt;

&lt;p&gt;Install the production extra so Waitress serves the app instead of Flask's development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your entry point (commonly &lt;code&gt;main.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-telegram-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render's start command should simply run this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Health Checks Explained
&lt;/h2&gt;

&lt;p&gt;StayPresent automatically exposes &lt;code&gt;/health&lt;/code&gt;, which always returns &lt;code&gt;{"status": "ok"}&lt;/code&gt;. In Render's dashboard, you can point the service's health check path at &lt;code&gt;/health&lt;/code&gt; instead of &lt;code&gt;/&lt;/code&gt;, keeping your root route free to serve a status page, a JSON payload, or nothing more than the default &lt;code&gt;{"message": "I'm Present"}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Ping to Avoid Idle Sleep
&lt;/h2&gt;

&lt;p&gt;Render's sleep timer only cares about incoming traffic. If nothing external is hitting your service, &lt;code&gt;staypresent.cron()&lt;/code&gt; can generate that traffic itself by pinging your own public URL on a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-app.onrender.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# every 4 minutes
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has to target your &lt;strong&gt;public&lt;/strong&gt; URL — pinging &lt;code&gt;0.0.0.0&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; never leaves the machine, so it won't count as activity from Render's perspective.&lt;/p&gt;

&lt;p&gt;You can also react to failures with a callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-app.onrender.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_failure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warm ping failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Crash Recovery on Render
&lt;/h2&gt;

&lt;p&gt;Bots crash — a bad API response, a network blip, an unhandled exception. By default, StayPresent restarts your bot process automatically on a non-zero exit code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_on_crash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_restarts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_reset_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;max_restarts&lt;/code&gt; only counts &lt;em&gt;consecutive&lt;/em&gt; crashes — if the bot stays up longer than &lt;code&gt;restart_reset_after&lt;/code&gt; (60 seconds by default), the counter resets to zero. If restarts are exhausted, StayPresent exits the whole process with the bot's original exit code, so Render's own platform-level restart policy can take over as a last resort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Working Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-app.onrender.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_on_crash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_restarts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Point Render's health check at &lt;code&gt;/health&lt;/code&gt;, not &lt;code&gt;/&lt;/code&gt;, if your root route serves anything custom.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;interval&lt;/code&gt; on &lt;code&gt;cron()&lt;/code&gt; comfortably below Render's ~15-minute idle window — 4–5 minutes is a safe default.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;prod&lt;/code&gt; extra in any real deployment so Waitress handles concurrency instead of Flask's dev server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pinging the local bind address instead of the public Render URL.&lt;/strong&gt; &lt;code&gt;staypresent.cron("0.0.0.0", port=8080)&lt;/code&gt; only tests that your own server responds locally; it does nothing to prevent Render's sleep timer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to read &lt;code&gt;$PORT&lt;/code&gt; from the environment.&lt;/strong&gt; Render assigns this dynamically — hardcoding &lt;code&gt;port=8080&lt;/code&gt; will work locally but may not match what Render expects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming self-ping bypasses Render's hard limits.&lt;/strong&gt; It only prevents inactivity sleep; it can't work around account-level suspensions or free-tier quotas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does self-ping guarantee 24/7 uptime?&lt;/strong&gt;&lt;br&gt;
It prevents inactivity-based sleep, but final availability still depends on Render's own policies and limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use the same setup on Railway?&lt;/strong&gt;&lt;br&gt;
Yes — Railway also injects &lt;code&gt;$PORT&lt;/code&gt; automatically, so the exact same &lt;code&gt;main.py&lt;/code&gt; works unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need Waitress?&lt;/strong&gt;&lt;br&gt;
No, it's optional, but recommended for production traffic. Without it, StayPresent falls back to Flask's built-in server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;render python bot&lt;/strong&gt; doesn't have to sleep. Pair Render's &lt;code&gt;$PORT&lt;/code&gt; requirement, StayPresent's built-in &lt;code&gt;/health&lt;/code&gt; endpoint, an optional self-ping via &lt;code&gt;cron()&lt;/code&gt;, and automatic crash recovery, and you get a bot that stays online, restarts itself when something goes wrong, and requires almost no boilerplate to set up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>automation</category>
      <category>deployment</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Full Guide on How to Keep Python Bot Alive with StayPresent</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:52:51 +0000</pubDate>
      <link>https://dev.to/john_wick_47/full-guide-on-how-to-keep-python-bot-alive-with-staypresent-4dep</link>
      <guid>https://dev.to/john_wick_47/full-guide-on-how-to-keep-python-bot-alive-with-staypresent-4dep</guid>
      <description>&lt;h1&gt;
  
  
  The Complete Guide to Keeping Python Bots Alive Using StayPresent
&lt;/h1&gt;

&lt;p&gt;If you've ever deployed a Telegram bot, Discord bot, or background worker on a free hosting tier, you've probably watched it die a slow, quiet death a few minutes after your last request. One moment it's responding instantly, the next it's completely unresponsive until someone happens to poke it again. This is the single most common problem developers run into when they try to keep a python bot alive on modern cloud platforms, and it has a well-understood cause and a genuinely simple fix.&lt;/p&gt;

&lt;p&gt;This guide walks through why it happens, and how a small package called &lt;strong&gt;StayPresent&lt;/strong&gt; solves it with one line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Python Bots Stop Running&lt;/li&gt;
&lt;li&gt;How Free Hosting Platforms Actually Work&lt;/li&gt;
&lt;li&gt;What a "Keep-Alive Server" Really Is&lt;/li&gt;
&lt;li&gt;Introducing StayPresent&lt;/li&gt;
&lt;li&gt;Installing StayPresent&lt;/li&gt;
&lt;li&gt;Your First Keep-Alive Bot&lt;/li&gt;
&lt;li&gt;The Built-In Health Endpoint&lt;/li&gt;
&lt;li&gt;Deploying to Render&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Python Bots Stop Running
&lt;/h2&gt;

&lt;p&gt;Most Python bots — Telegram bots built with Pyrogram or python-telegram-bot, Discord bots built with discord.py, scrapers, automation scripts — are designed to run forever in a loop. They don't naturally open an HTTP port; they just sit there, listening for events or polling an API.&lt;/p&gt;

&lt;p&gt;The problem is that most free-tier hosting providers were never built for that kind of workload. They were built to serve web traffic. So they monitor for one specific signal: is something listening on an HTTP port? If nothing answers on that port for a while, the platform assumes your service is idle, and it either spins the container down or marks it unhealthy and restarts it — often destroying whatever in-memory state your bot was holding onto.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Free Hosting Platforms Actually Work
&lt;/h2&gt;

&lt;p&gt;Platforms like Render, Railway, Koyeb, and similar services generally fall into two buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web services&lt;/strong&gt;, which expect an HTTP server bound to a &lt;code&gt;PORT&lt;/code&gt; environment variable, and which the platform actively health-checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background workers&lt;/strong&gt;, which don't need a port, but often come with stricter limits or aren't available at all on the free tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you deploy a bot as a web service without an HTTP server, the platform will typically flag it as unhealthy because nothing responds on the expected port. This is the root cause behind most "my bot randomly stops working" reports you'll find in developer communities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a "Keep-Alive Server" Really Is
&lt;/h2&gt;

&lt;p&gt;The fix is conceptually simple: run a tiny HTTP server &lt;em&gt;alongside&lt;/em&gt; your bot, purely so the hosting platform has something to talk to. The server doesn't need to do anything meaningful — it just needs to exist and respond to requests.&lt;/p&gt;

&lt;p&gt;Developers have hacked this together for years with a few lines of raw Flask, spun up in a background thread next to their bot's main loop. It works, but it means every bot ends up with its own copy of the same boilerplate: server setup, threading, process management, and eventually crash recovery once the bot starts failing in production.&lt;/p&gt;

&lt;p&gt;That repeated boilerplate is exactly what StayPresent replaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing StayPresent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;StayPresent&lt;/strong&gt; is a lightweight Python package that runs a Flask-powered HTTP server next to your bot script, with optional production-grade serving through Waitress. Instead of writing your own threading and process-management code, you call one function and StayPresent handles the rest — starting the server, running your bot script as a subprocess, and restarting it automatically if it crashes.&lt;/p&gt;

&lt;p&gt;It's built specifically for platforms that require an active HTTP port, including Render, Railway, Koyeb, and Heroku, and it works just as well inside Docker or on a plain VPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing StayPresent
&lt;/h2&gt;

&lt;p&gt;Standard installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production deployments, install the &lt;code&gt;prod&lt;/code&gt; extra, which pulls in Waitress and suppresses Flask's development-server warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Your First Keep-Alive Bot
&lt;/h2&gt;

&lt;p&gt;Here's the entire setup needed to keep a bot alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;staypresent.run()&lt;/code&gt; starts the HTTP server and launches &lt;code&gt;bot.py&lt;/code&gt; as a subprocess at the same time. If you don't configure a response at all, StayPresent defaults to returning &lt;code&gt;{"message": "I'm Present"}&lt;/code&gt; on the root route, so even the laziest possible setup still gives the platform something to check.&lt;/p&gt;

&lt;p&gt;You can also serve plain text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service Operational&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or a full HTML page, read fresh from disk on every request, along with any CSS/JS/images sitting next to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;templates/index.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Built-In Health Endpoint
&lt;/h2&gt;

&lt;p&gt;StayPresent automatically exposes a dedicated &lt;code&gt;/health&lt;/code&gt; route that always returns &lt;code&gt;{"status": "ok"}&lt;/code&gt;. This is separate from whatever you've configured at &lt;code&gt;/&lt;/code&gt;, so you can use &lt;code&gt;/&lt;/code&gt; for a human-facing status page and &lt;code&gt;/health&lt;/code&gt; specifically for platform health checks and uptime monitors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Render
&lt;/h2&gt;

&lt;p&gt;Render assigns the port to listen on through the &lt;code&gt;PORT&lt;/code&gt; environment variable, so your entry point should read it dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your start command is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Railway follows the exact same pattern since it also injects &lt;code&gt;$PORT&lt;/code&gt; automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always bind to &lt;code&gt;0.0.0.0&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;127.0.0.1&lt;/code&gt;, so the platform's health checker can actually reach the server. This is StayPresent's default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the port from the environment&lt;/strong&gt; on any platform that assigns one dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep &lt;code&gt;restart_on_crash&lt;/code&gt; enabled&lt;/strong&gt; (it's on by default) so a bad exception in your bot doesn't take the whole service down permanently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate your logging&lt;/strong&gt; — StayPresent logs under its own &lt;code&gt;"staypresent"&lt;/code&gt; logger and never touches your root logger, so you can freely raise or lower its verbosity without affecting your bot's own logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Passing a bare string to &lt;code&gt;bot_args&lt;/code&gt; instead of a list.&lt;/strong&gt; &lt;code&gt;bot_args="--verbose"&lt;/code&gt; will not behave the way you expect — pass &lt;code&gt;["--verbose"]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming a keep-alive server prevents platform shutdowns.&lt;/strong&gt; It only stops the platform from marking your service &lt;em&gt;idle&lt;/em&gt;. It won't override hard usage limits, account suspensions, or free-tier quotas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the health endpoint exists.&lt;/strong&gt; Some platforms let you point their health check specifically at &lt;code&gt;/health&lt;/code&gt; instead of &lt;code&gt;/&lt;/code&gt;, which is worth doing if your root route serves a dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does StayPresent replace Flask?&lt;/strong&gt;&lt;br&gt;
No — it wraps Flask (and optionally Waitress) to handle the hosting boilerplate for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it host my application?&lt;/strong&gt;&lt;br&gt;
No. It runs inside whatever hosting environment you've already chosen; you still need to deploy to Render, Railway, a VPS, or similar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it keep my app alive forever?&lt;/strong&gt;&lt;br&gt;
No single tool can guarantee that. StayPresent solves the HTTP-port problem and can optionally self-ping to avoid inactivity sleep, but it can't override hard platform limits.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Keeping a Python bot alive on modern cloud platforms almost always comes down to one thing: giving the platform an HTTP port to check. StayPresent turns that requirement from a recurring chore into a single line of code, while also handling crash recovery and production-grade serving along the way.&lt;/p&gt;

&lt;p&gt;If you're tired of rewriting the same Flask keep-alive snippet for every new bot, install StayPresent and get back to building the bot itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>automation</category>
      <category>python</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
