If you’re using Supabase’s generous free tier for side projects, you’ve probably seen the dreaded email: your project will be paused due to inactivity. The rule is simple—if no API requests hit your database for 7 days, Supabase assumes the project is abandoned and pauses it to save resources. No amount of dashboard browsing counts. You need actual REST API calls.

The fix? A tiny scheduled task that pings your database a couple of times a week. You could spin up a server, use an external cron service, or—my favorite—just use GitHub Actions. You already have a repo (or can create one in 30 seconds), and it’s completely free for public repositories (and even private repos get generous free minutes).

Here’s exactly how to set up a “keep alive” cron with GitHub Actions, step by step.

What You’ll Need

  • A Supabase project on the free plan
  • A GitHub account
  • 5 minutes

Step 1: Get Your Supabase Credentials

From your Supabase dashboard, go to Project Settings → API. Copy two values:

  • Project URL (e.g., https://abcxyz.supabase.co)
  • anon public key (starts with eyJ...)

We’ll use the anon key because we only need to read one row from a table. Never use the service_role key here—it has full admin rights and should stay secret.

Step 2: Create (or Reuse) a GitHub Repository

You can add the workflow to an existing project repo, or create a brand-new one just for this task. Even an empty repository works fine.

Step 3: Store Your Supabase Credentials as Secrets

We don’t hardcode keys. Instead, we’ll add them to GitHub so the workflow can access them safely.

  1. Go to your repo on GitHub → SettingsSecrets and variablesActions.

  2. Click New repository secret and add:

  • Name: SUPABASE_URL
  • Value: https://abcxyz.supabase.co

    Click **Add secret, then repeat for the second secret:**

  • Name: SUPABASE_KEY

  • Value: your anon key

You should now see both listed under “Repository secrets”.

Step 4: Create the Workflow File

In your repository, create a file at exactly:
.github/workflows/keep_alive.yml

text

Paste this YAML:

name: Keep Supabase Alive

on:
  schedule:
    # Runs every day at midnight UTC
    - cron: '0 0 * * *'
  workflow_dispatch: # allows manual triggering from the Actions tab

jobs:
  ping:
    runs-on: ubuntu-latest
    steps:
      - name: Ping Supabase Database
        run: |
          curl -X GET "${{ secrets.SUPABASE_URL }}/rest/v1/your_table_name?select=id&limit=1" \
            -H "apikey: ${{ secrets.SUPABASE_KEY }}" \
            -H "Authorization: Bearer ${{ secrets.SUPABASE_KEY }}"

Enter fullscreen mode Exit fullscreen mode

Important: Replace your_table_name with the actual name of a table in your Supabase database. The table must have an id column (or adjust the select parameter).

The schedule 0 0 * * * means midnight UTC daily—well within the 7-day limit. Even twice a week (0 0 * * 0,5 for Sunday and Friday) is enough, but daily is harmless and easy to remember.

The workflow_dispatch line lets you run the job manually to test it, which we’ll do next.

Step 5: Check Row Level Security (RLS)

By default, Supabase enables Row Level Security on new tables. The anon key will only succeed if there’s a policy that allows SELECT for the public role (or anon). If the table is empty or RLS is blocking reads, the curl will fail with a 401 or 403.

Quick check in Supabase SQL Editor:

-- Check if RLS is enabled on your table
SELECT relname, relrowsecurity
FROM pg_class
WHERE relname = 'your_table_name';

-- See which policies exist for that table
SELECT polname, polcmd, roles
FROM pg_policy
WHERE polrelid = 'your_table_name'::regclass;

Enter fullscreen mode Exit fullscreen mode

roles showing {0} means “public” (which includes the anon key).

polcmd = 'r' means SELECT.

If you see no policy and RLS is true, create one:

CREATE POLICY "Allow public read"
ON your_table_name
FOR SELECT
TO anon
USING (true);

Enter fullscreen mode Exit fullscreen mode

Now the anon key can read rows from that table.

Step 6: Test the Workflow Manually

Don’t wait for the cron to fire—test it now.

Go to your repo → Actions tab.

Click Keep Supabase Alive in the left sidebar.

Click the Run workflow button → Run workflow.

A new run will appear in a few seconds. Click on it, then click the ping job, and expand the Ping Supabase Database step. If you see a JSON response (even []) without errors, it’s working.

You can also verify in Supabase under Logs & Analytics by running:

SELECT timestamp, event_message
FROM edge_logs
ORDER BY timestamp DESC
LIMIT 5;

Enter fullscreen mode Exit fullscreen mode

You should see a REST request matching the time of your test.

What You’ve Achieved

You now have a cost-free, zero-maintenance heartbeat for your Supabase project. Every day (or however you set the cron) GitHub Actions will ping your database with a harmless SELECT, resetting the inactivity timer.

A few notes to keep things clean:

Don’t hammer the endpoint; one request per day is plenty.

This approach has kept my side projects alive for months without a single pause email. Hopefully it does the same for yours.

Got questions or a different method you use? Drop a comment!