GeofenceKit Start free
iOS · Core Location

The iOS 20-Region Limit — and How to Monitor Unlimited Geofences

24 July 2026 · ~7 min read
TL;DR: iOS lets each app monitor at most 20 geofence regions at a time with Core Location. The fix isn't a hack to raise the cap — it's to keep all your zones on a server and register only the nearest ~18 on the device, re-syncing as the user moves. That gives you effectively unlimited geofences within the OS limit.

You add geofencing to an iOS app, it works great with a handful of zones… and then you add your 21st store, campus, or delivery area and it silently stops firing for the new ones. This is one of the most common — and most confusing — walls in mobile location work. Here's exactly what the limit is, why it exists, and the pattern that scales past it.

What the limit actually is

iOS region monitoring runs through CLLocationManager. You register a circular region with startMonitoring(for:), and the OS watches it for you — even when your app is suspended or not running — waking you on enter/exit. That "even when the app isn't running" magic is exactly why it's capped: the system, not your app, holds the regions.

Apple's documented limit is 20 regions per app. Register a 21st and you don't get an exception — you get a locationManager(_:monitoringDidFailFor:withError:) callback (often kCLErrorRegionMonitoringFailure), and that region simply isn't watched. It fails quietly, which is why so many teams ship it and only discover the gap in production.

Why it exists (and why "just raise it" isn't an option)

Region monitoring is a shared system resource. The OS keeps your regions armed 24/7 against low-power location signals so it can wake a suspended app. Twenty is the budget Apple gives each app so the device's battery and the shared monitoring subsystem stay sane across every installed app. There's no entitlement or API to increase it — the number is fixed.

Workarounds that don't scale

The pattern that works: server-managed “nearest-N” sync

The scalable approach separates where your zones are (unlimited, on a server) from what the device monitors right now (the nearest handful, within the OS cap):

  1. Keep the full zone list server-side — thousands of them, no device limit.
  2. On launch and on movement, ask the server for the nearest N zones to the user (register ~18, not the full 20 — leave headroom for transient regions).
  3. Register exactly those with startMonitoring(for:); remove the ones that dropped out of range.
  4. Re-sync as the user travels. Use significant-location-change (or a "re-sync after X meters" rule) to re-fetch the nearest set when they move far enough that a different zone is now closest.

To the user it looks like every zone is always monitored; in reality the device is always watching the 18 that could plausibly matter next.

A minimal Swift sketch

func syncNearest(to location: CLLocationCoordinate2D) async {
    // 1) Ask your backend for the nearest zones (server holds them all).
    let zones = try await api.nearestZones(lat: location.latitude,
                                           lng: location.longitude,
                                           limit: 18)

    // 2) Clear what's currently monitored, register the fresh set.
    for region in manager.monitoredRegions { manager.stopMonitoring(for: region) }
    for z in zones {
        let region = CLCircularRegion(
            center: CLLocationCoordinate2D(latitude: z.lat, longitude: z.lng),
            radius: z.radius, identifier: z.id)
        region.notifyOnEntry = true
        region.notifyOnExit  = true
        manager.startMonitoring(for: region)
    }
}

// Re-run syncNearest() on significant location change so the
// "nearest 18" follows the user as they travel.

What about polygon zones?

Core Location only monitors circles — there's no polygon region API. The workaround: store the polygon on the server, register its bounding circle on the device so the OS still wakes you nearby, and run a point-in-polygon check against the user's actual coordinate on entry to confirm they're truly inside before you act. You get arbitrary shapes without giving up OS-level wake-ups.

Gotchas worth handling: re-register everything on app relaunch and after BOOT/updates; debounce boundary flapping (GPS jitter at the edge firing enter/exit repeatedly); apply a dwell threshold so a quick pass-through doesn't count; and remember background enter/exit needs Always location permission.

The short version

The 20-region cap is permanent, so stop fighting it. Hold every zone on a server, register only the nearest ~18 on the device, and re-sync as the user moves. Add point-in-polygon for non-circular areas and debounce the edges. That's the whole trick to "unlimited" geofences on iOS — and the same idea (with its own quirks) is what you need on Android too.

Don't want to build all that?

GeofenceKit does exactly this — server-managed nearest-N sync, polygon support, offline queue, background delivery — as one SDK for iOS, Android, React Native, and Flutter. Register thousands of zones; we keep each device within the OS limit for you.

Start free — get an API key

Related: Android geofences & reboot · Background geofencing in React Native