DEV Community

Cover image for Keeping Your Streamlit App Awake with Serverless Functions and a Free Cron Job
Abhishek Choudhary
Abhishek Choudhary

Posted on

Keeping Your Streamlit App Awake with Serverless Functions and a Free Cron Job

You deploy a little Streamlit app on Community Cloud, share the link, and a day later it's showing a sleepy "Zzzz, this app has gone to sleep due to inactivity" page. Anyone who opens it has to click a button and wait for a cold start.

There's no keep-alive switch in Streamlit Community Cloud, and the advice on the forums boils down to "push an empty commit" or "automate a browser". Both work, but both are heavier than they need to be. There's a simpler way: do exactly what that button does, with one HTTP request, on a schedule.

⚠️ This uses an undocumented endpoint that the Streamlit web page itself calls. It can change without notice. Only use it on apps you own, and keep the schedule gentle.

What the "wake up" button really does

If you open dev tools on a sleeping app and click Yes, get this app back up!, you'll see a single request:

POST https://<your-app>.streamlit.app/api/v2/app/resume
Enter fullscreen mode Exit fullscreen mode

It needs two small things besides the POST itself, and neither one is a login:

  1. A CSRF token. The page never stores it anywhere fancy. It simply remembers the x-csrf-token response header from the previous API call and sends it back. The server also sets a matching _streamlit_csrf cookie on that same response.
  2. A machine id. A random UUID sent as x-streamlit-machine-id.

So the whole dance is: ask for the app status, grab the token and cookie, then POST to resume.

Try it with curl

I'm using webpie.streamlit.app here, swap in your own app URL. Save this as wake.sh:

#!/usr/bin/env bash
# Wake a sleeping Streamlit Community Cloud app.
# Usage: ./wake.sh https://webpie.streamlit.app
set -euo pipefail

APP_URL="${1:?usage: ./wake.sh https://webpie.streamlit.app}"
COOKIES="$(mktemp)"
trap 'rm -f "$COOKIES"' EXIT

# Step 1: any API call hands us a CSRF cookie (saved in the jar)
# and an x-csrf-token response header (we grab it here).
CSRF_TOKEN=$(
  curl --silent --output /dev/null --dump-header - \
    --cookie-jar "$COOKIES" \
    "$APP_URL/api/v2/app/status" \
  | tr -d '\r' \
  | awk -F': ' 'tolower($1) == "x-csrf-token" { print $2 }'
)

# Step 2: the browser also sends a random UUID as a machine id.
MACHINE_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')

# Step 3: this is the call behind the "Yes, get this app back up!" button.
curl --silent --include --request POST \
  --cookie "$COOKIES" --cookie-jar "$COOKIES" \
  --header "x-csrf-token: $CSRF_TOKEN" \
  --header "x-streamlit-machine-id: $MACHINE_ID" \
  --header "origin: $APP_URL" \
  --header "referer: $APP_URL/" \
  "$APP_URL/api/v2/app/resume"
echo

# Step 4 (optional): check the status afterwards
curl --silent --cookie "$COOKIES" "$APP_URL/api/v2/app/status"
echo
Enter fullscreen mode Exit fullscreen mode

Run it:

chmod +x wake.sh
./wake.sh https://webpie.streamlit.app
Enter fullscreen mode Exit fullscreen mode

A 204 No Content from the resume call means the request was accepted. The status call at the end shows the app's state; while it's booting, the status number changes. (An app that's already awake just answers normally, so it's safe to run any time.)

Make it run itself

A shell script on your laptop doesn't help while you're asleep too. The nice part is that the logic is just fetch calls, so it runs on almost any serverless platform: Cloudflare Workers, Vercel, Netlify, AWS Lambda, Deno Deploy. Pick whichever you already use.

Here's the core logic once, in plain JavaScript:

// Shared logic: wake a Streamlit Community Cloud app.
// Works in Cloudflare Workers, Vercel, Netlify, Node 18+, anything with fetch.
export async function wakeApp(appUrl) {
  // Step 1: any API call gives us a CSRF cookie and an x-csrf-token header.
  const status = await fetch(`${appUrl}/api/v2/app/status`);
  const csrfToken = status.headers.get("x-csrf-token");
  const cookies = status.headers
    .getSetCookie()
    .map((cookie) => cookie.split(";")[0]) // keep "name=value", drop attributes
    .join("; ");

  // Step 2: this is the call behind the "Yes, get this app back up!" button.
  const resume = await fetch(`${appUrl}/api/v2/app/resume`, {
    method: "POST",
    headers: {
      cookie: cookies,
      "x-csrf-token": csrfToken,
      "x-streamlit-machine-id": crypto.randomUUID(),
      origin: appUrl,
      referer: `${appUrl}/`,
    },
  });

  return { ok: resume.ok, status: resume.status };
}
Enter fullscreen mode Exit fullscreen mode

Option A: Cloudflare Workers

worker.js:

import { wakeApp } from "./wake.js";

export default {
  async fetch(request, env) {
    // Optional: stop strangers from poking your endpoint
    const secret = new URL(request.url).searchParams.get("key");
    if (env.WAKE_KEY && secret !== env.WAKE_KEY) {
      return new Response("Nope", { status: 401 });
    }

    const result = await wakeApp(env.APP_URL);
    return Response.json(result);
  },
};
Enter fullscreen mode Exit fullscreen mode

Set APP_URL (for example https://webpie.streamlit.app) and an optional WAKE_KEY as variables in the Worker settings, then deploy with npx wrangler deploy. You'll get a URL like https://wake-streamlit.<you>.workers.dev/?key=<your key>.

Option B: Vercel

Put wake.js in your project root and add api/wake.js:

import { wakeApp } from "../wake.js";

export default async function handler(req, res) {
  if (process.env.WAKE_KEY && req.query.key !== process.env.WAKE_KEY) {
    return res.status(401).send("Nope");
  }

  const result = await wakeApp(process.env.APP_URL);
  res.status(200).json(result);
}
Enter fullscreen mode Exit fullscreen mode

Add APP_URL and WAKE_KEY under Project Settings → Environment Variables and deploy. Your endpoint is https://<project>.vercel.app/api/wake?key=<your key>.

Trigger it with cron-job.org (free)

You don't even need the platform's own scheduler. cron-job.org is free and will call any URL on a schedule:

  1. Create an account and click Create cronjob.
  2. URL: the Worker or Vercel endpoint from above, including ?key=....
  3. Schedule: every 6 hours is plenty. Apps seem to sleep after roughly 12 hours without visitors, so you want to land well inside that window.
  4. Save. Under the job's history you can see each run's status code. 200 means your function ran.

That's the whole setup: cron-job.org pokes your serverless function, the function pokes Streamlit, and your app stays warm. No browser, no Selenium, no fake commits.

Things worth knowing

  • It's unofficial. If Streamlit changes the endpoint, the script breaks. Check the cron history now and then.
  • It's polite use only. One request every few hours for your own app. Please don't hammer it.
  • Waking isn't the same as being busy. This resumes a sleeping app. It doesn't pretend to be traffic, so the app can still go back to sleep later, which is why the schedule keeps repeating.
  • Keep the key secret so nobody else can trigger your function.

If you'd rather stay inside the official lines, the alternative is hosting the app somewhere that doesn't sleep. But for a free-tier hobby app, a 20-line function and a free cron service is hard to beat.

Happy hacking!

Top comments (0)