Scheduled Scans
AllyProof can automatically scan your sites on a recurring schedule. Scheduled scans run in the background and send notifications when complete, so you always have up-to-date accessibility data without manual intervention.
How Scheduling Works
Scheduled scans use PostgreSQL's pg_cron extension inside Supabase. A cron job fires at the configured interval, calls an internal scan trigger endpoint, which then queues scans for all eligible sites in the organization.
pg_cron (Supabase)
├── Fires at scheduled time
├── Calls POST /api/internal/trigger-scheduled-scans
│ (authenticated with CRON_SECRET)
├── Endpoint queries sites where scan_frequency matches
└── Queues a scan job for each matching siteSetting Scan Frequency
Each organization has a scan_frequency setting that determines how often automatic scans run. This is configured in Settings > Scanning:
| Frequency | Schedule | Available on |
|---|---|---|
daily | Every day at 03:00 UTC | Agency and Enterprise plans |
weekly | Every Monday at 03:00 UTC | All plans (Starter, Agency, Enterprise) |
manual | No automatic scans | All plans |
The 03:00 UTC time slot is chosen to minimize interference with peak traffic hours across most time zones and to avoid scanning during business-hours deployments.
What Happens During a Scheduled Scan
- pg_cron fires — The cron job executes at the scheduled time and sends a POST request to the internal trigger endpoint.
- Site selection — The endpoint queries for all sites belonging to organizations with matching
scan_frequencyand active subscriptions. Sites that are unverified or paused are skipped. - Scan queuing — A scan job is created for each eligible site. Jobs are queued with a staggered start (30-second intervals) to avoid overwhelming the scanner workers.
- Scanning — Each scan follows the standard scan pipeline: page discovery, multi-engine analysis (axe-core + HTML_CodeSniffer + APCA), deduplication, and AI fix suggestion generation.
- Notification — When each scan completes, an email notification is sent to organization members with notifications enabled (see Email Notifications).
pg_cron Configuration
The cron jobs are set up in Supabase using the pg_cron extension. Two jobs are configured:
-- Daily scan trigger (runs every day at 03:00 UTC)
SELECT cron.schedule(
'trigger-daily-scans',
'0 3 * * *',
$$
SELECT net.http_post(
url := current_setting('app.settings.app_url') || '/api/internal/trigger-scheduled-scans',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || current_setting('app.settings.cron_secret')
),
body := '{"frequency": "daily"}'::jsonb
);
$$
);
-- Weekly scan trigger (runs every Monday at 03:00 UTC)
SELECT cron.schedule(
'trigger-weekly-scans',
'0 3 * * 1',
$$
SELECT net.http_post(
url := current_setting('app.settings.app_url') || '/api/internal/trigger-scheduled-scans',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || current_setting('app.settings.cron_secret')
),
body := '{"frequency": "weekly"}'::jsonb
);
$$
);CRON_SECRET Setup
The internal trigger endpoint is protected by a shared secret to prevent unauthorized scan triggering. Set up the CRON_SECRET environment variable:
- Generate a random secret:
openssl rand -hex 32 - Add it to your
.env.localfile:CRON_SECRET=your-generated-secret-here - Set it in Supabase vault or app settings so pg_cron can reference it:
ALTER DATABASE postgres SET app.settings.cron_secret = 'your-generated-secret-here';
The trigger endpoint validates the secret from the Authorization header and returns 401 Unauthorized if it does not match.
Monitoring Scheduled Scans
You can monitor scheduled scan activity in several ways:
- Scan history — The site detail page shows all scans with their trigger source (manual, scheduled, or CI/CD)
- pg_cron logs — Check
cron.job_run_detailsin Supabase SQL editor to see job execution history and any errors - Email notifications — Enable scan completion notifications to get results delivered to your inbox
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Scans not running | pg_cron extension not enabled | Enable via Supabase Dashboard > Database > Extensions |
| 401 errors in cron logs | CRON_SECRET mismatch | Verify the secret matches between app env and Supabase settings |
| Sites skipped | Unverified or paused sites | Verify site ownership and check the site is not paused |
| Scans queued but not completing | Scanner worker capacity | Check Fly.io machine status and scale up if needed |