{"id":18133,"date":"2026-07-13T12:29:05","date_gmt":"2026-07-13T05:29:05","guid":{"rendered":"https:\/\/www.cipher.co.th\/blogs\/cloudflare-hyperdrive-postgres-2\/"},"modified":"2026-07-13T12:29:05","modified_gmt":"2026-07-13T05:29:05","slug":"cloudflare-hyperdrive-postgres","status":"publish","type":"post","link":"https:\/\/www.cipher.co.th\/en\/blogs\/cloudflare-hyperdrive-postgres\/","title":{"rendered":"Cloudflare Hyperdrive: Keep Your Existing Postgres, Move Your Compute"},"content":{"rendered":"<p><strong>Quick answer:<\/strong> Hyperdrive is a connection pooler and query cache that sits between Cloudflare Workers and the PostgreSQL or MySQL database you already run. It exists because a Worker can open only 6 outgoing connections per invocation, and because traditional databases have a hard connection ceiling that a globally distributed serverless runtime will exhaust in minutes. You do not migrate your database, change your schema, or drop any extensions \u2014 you take your existing connection string, create a config, and bind it to your Worker. The one thing you must understand before switching it on is that the cache does not invalidate on write.<\/p>\n<p>The single biggest reason engineering teams reject Cloudflare Workers is the database. They already run PostgreSQL. The schema is stable, the reports depend on it, the backups have been tested, and someone knows how to restore it. What they want is to move compute to the edge \u2014 lower latency, no idle servers to pay for \u2014 without touching the part of the stack that works.<\/p>\n<p>The problem is that connecting a Worker directly to Postgres hits a wall almost immediately. A Worker gets 6 simultaneous outgoing connections per invocation, and Workers run in thousands of short-lived isolates spread across Cloudflare network. If every invocation opens a fresh connection to your database, you will exhaust its connection limit within minutes of real traffic.<\/p>\n<p>Hyperdrive exists specifically to fix this. This article covers what it actually does, its real limits, and the trap you will fall into if you leave the default cache settings on without reading the documentation. All numbers are from Cloudflare official documentation, checked as of July 2026.<\/p>\n<h2>The problem Hyperdrive solves<\/h2>\n<h3>Six connections per invocation<\/h3>\n<p>Workers cap simultaneous outgoing connections at 6 per invocation. The implication is that a Worker is not a long-lived server process that can hold a connection pool in memory. It is an isolate that spins up per request and disappears, with 128 MB of memory that is not shared across invocations.<\/p>\n<p>Translation: the connection pool that every backend engineer is used to \u2014 open 20 connections at boot, reuse them for hours \u2014 does not exist here. When traffic arrives from 300-plus cities at once, each isolate that needs the database tries to open its own connection, and Postgres eventually answers with <code>too many connections<\/code>.<\/p>\n<h3>Connection setup costs multiple round trips<\/h3>\n<p>Opening a PostgreSQL connection is not one request. It is a TCP handshake, then a TLS handshake, then the Postgres protocol authentication exchange. If your Worker runs at the edge in Bangkok and your database is in another region, every one of those round trips crosses the ocean before the first query even goes out.<\/p>\n<p>Hyperdrive moves that connection setup as close to your Worker as possible, and keeps the pool of real database connections near your database. Worker-to-Hyperdrive setup is fast because it happens within the same location; Hyperdrive-to-database is a warm connection that already exists.<\/p>\n<h2>What Hyperdrive actually does<\/h2>\n<h3>One: edge connection setup<\/h3>\n<p>Instead of your Worker completing a handshake across a continent, it completes a handshake with Hyperdrive in the same location. The latency of the setup phase largely disappears.<\/p>\n<h3>Two: connection pooling near the database<\/h3>\n<p>Hyperdrive maintains a pool in one or more regions closest to your origin database, and caps the number of connections it opens to it: roughly 20 per configuration on the Free plan, roughly 100 on Paid. Notably, Hyperdrive does not limit how many client connections your Workers make to it. You can have as many isolates as you like; they all funnel into a bounded pool.<\/p>\n<p>Cloudflare is honest about one caveat here: Hyperdrive is a distributed system, and if a client cannot reach an existing pool it will establish a new one with its own connection allocation. Actual connection counts may occasionally exceed the listed limits, because the system prioritises availability over strict enforcement. Leave headroom in your Postgres <code>max_connections<\/code>.<\/p>\n<h3>Three: query caching<\/h3>\n<p>Hyperdrive parses the database wire protocol to distinguish a mutating query from a non-mutating one, and caches responses only for eligible reads. Defaults are <code>max_age<\/code> of 60 seconds and <code>stale_while_revalidate<\/code> of 15 seconds, and you can raise <code>max_age<\/code> to a maximum of 1 hour. Caching is on by default.<\/p>\n<h2>The real limits<\/h2>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Limit<\/th>\n<th>Workers Free<\/th>\n<th>Workers Paid<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Configured databases per account<\/td>\n<td>10<\/td>\n<td>25<\/td>\n<\/tr>\n<tr>\n<td>Database queries<\/td>\n<td>100,000 \/ day<\/td>\n<td>Unlimited<\/td>\n<\/tr>\n<tr>\n<td>Origin database connections (per config)<\/td>\n<td>~20<\/td>\n<td>~100<\/td>\n<\/tr>\n<tr>\n<td>Initial connection timeout<\/td>\n<td>15 seconds<\/td>\n<td>15 seconds<\/td>\n<\/tr>\n<tr>\n<td>Idle connection timeout<\/td>\n<td>10 minutes<\/td>\n<td>10 minutes<\/td>\n<\/tr>\n<tr>\n<td>Maximum query duration<\/td>\n<td>60 seconds<\/td>\n<td>60 seconds<\/td>\n<\/tr>\n<tr>\n<td>Maximum cached query response<\/td>\n<td>50 MB<\/td>\n<td>50 MB<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>The one people trip over is 25 configurations per account on Paid. If you want one cached config and one cache-disabled config per database \u2014 which you probably do, for reasons covered below \u2014 that is two configs per database. Also note that a response larger than 50 MB is not cached, but is still returned to your Worker normally.<\/p>\n<h2>Pricing, as of July 2026<\/h2>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Item<\/th>\n<th>Workers Free<\/th>\n<th>Workers Paid<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Database queries via Hyperdrive<\/td>\n<td>100,000 \/ day (resets 00:00 UTC)<\/td>\n<td>Unlimited<\/td>\n<\/tr>\n<tr>\n<td>Connection pooling<\/td>\n<td>Included<\/td>\n<td>Included, no additional charge<\/td>\n<\/tr>\n<tr>\n<td>Query caching<\/td>\n<td>Included<\/td>\n<td>Included, no additional charge<\/td>\n<\/tr>\n<tr>\n<td>Egress \/ data transfer<\/td>\n<td>No charge<\/td>\n<td>No charge<\/td>\n<\/tr>\n<tr>\n<td>Do cache hits count as queries<\/td>\n<td>Yes<\/td>\n<td>Yes (but Paid is unlimited)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>In short: on Workers Paid ($5\/month), Hyperdrive adds nothing to your bill. You still pay for your Postgres as before, and for Workers as normal. That makes the ROI calculation unusually simple, because the marginal cost of adding Hyperdrive is zero.<\/p>\n<h2>A worked example: e-commerce with Postgres already in production<\/h2>\n<p>Say you run an online retailer selling into Thailand and the rest of Southeast Asia. Your PostgreSQL sits in <code>ap-southeast-1<\/code>. The catalog holds 200,000 SKUs. You use PostGIS to find the nearest store to a buyer. Finance has written a pile of long, gnarly SQL reports against it.<\/p>\n<p>Migrating all of this to D1 is a non-starter \u2014 PostGIS alone rules it out. But what you do want is product pages rendered from the edge, without moving the database at all.<\/p>\n<h3>What you build<\/h3>\n<p>You create two Hyperdrive configurations. The first has caching enabled, for reads that tolerate a short stale window: product listings, category pages, search results. The second is created with <code>--caching-disabled<\/code>, for everything where stale data is a bug: authentication, sessions, permissions, cart contents, inventory counts at checkout, and any read that immediately follows a write.<\/p>\n<p>Bind both to the same Worker, construct a separate database client per binding, and pass the cache-disabled client to your auth and checkout modules while the cached client serves the catalog. This matters especially if an ORM owns your SQL, because the ORM writes the query, not you \u2014 the only thing you control is which client it uses.<\/p>\n<h3>What you get<\/h3>\n<p>Repeated catalog queries are answered from cache at the edge, without a round trip to the database, and the load on Postgres drops correspondingly. Connections are bounded by Hyperdrive during traffic spikes instead of piling up. You did not stand up PgBouncer, you do not operate it, and you do not patch it.<\/p>\n<h3>What to watch<\/h3>\n<p>If your Worker issues several sequential queries per request, running it at the edge means each query pays a cross-ocean round trip in series. Cloudflare documents the difference bluntly: 20-30 ms per query from a distant region, versus 1-3 ms when the Worker is placed near the database. The fix is Smart Placement \u2014 run the Worker close to the database instead of close to the user. But if your Worker makes only one query per request, Placement does not help, because the total round-trip time is the same either way.<\/p>\n<h2>The traps you need to know before turning it on<\/h2>\n<h3>The cache does not invalidate when you write<\/h3>\n<p>This is the one that bites teams hardest. Cloudflare documentation is explicit: Hyperdrive does not purge or invalidate cached read query results when your application writes to your database. A later matching <code>SELECT<\/code> can return the cached result until <code>max_age<\/code> expires, and can keep serving it during the <code>stale_while_revalidate<\/code> window while it refreshes in the background.<\/p>\n<p>If you leave caching on at defaults and point it at your permissions table or your session table, you have just created a security hole with a 60-second fuse. Cloudflare guidance is direct: use a cache-disabled configuration for authentication, sessions, permissions, billing state, admin settings, and reads immediately after a write.<\/p>\n<h3>Functions like NOW() make a query uncacheable<\/h3>\n<p>Hyperdrive only caches queries built from functions PostgreSQL classifies as <code>IMMUTABLE<\/code>. Functions classified <code>STABLE<\/code> or <code>VOLATILE<\/code> \u2014 <code>NOW()<\/code>, <code>CURRENT_DATE<\/code>, <code>CURRENT_TIMESTAMP<\/code>, <code>RANDOM()<\/code>, <code>LASTVAL()<\/code> \u2014 make the whole query uncacheable.<\/p>\n<p>So a dashboard query written as <code>WHERE created_at &gt; NOW() - INTERVAL 1 hour<\/code> will never be cached, not once. The fix is to compute the timestamp in application code and pass it in as a parameter. And do not write those function names in SQL comments either: Cloudflare warns that its parser uses text pattern matching and may treat a query as uncacheable because of a comment.<\/p>\n<h3>Transaction pooling means SET does not persist<\/h3>\n<p>Hyperdrive pools in transaction mode. A connection returns to the pool when the transaction completes, and is then <code>RESET<\/code>. Your <code>SET<\/code> statements do not carry over to the next query. You must re-issue them per transaction, or bundle the <code>SET<\/code> into the same query. Cloudflare also explicitly advises against wrapping many operations in one long transaction just to preserve state \u2014 doing so pins a connection for the whole duration and defeats the point of pooling.<\/p>\n<h2>When a direct connection, or a server in the same region, is the better choice<\/h2>\n<p>Hyperdrive is not the right answer for everyone.<\/p>\n<p>If your backend is a long-running server sitting in the same region as your database \u2014 an API on ECS or EC2 in <code>ap-southeast-1<\/code> \u2014 you already have an in-process connection pool and single-digit millisecond latency to the database. Putting Hyperdrive in the middle adds a hop and buys you nothing. Do not do it.<\/p>\n<p>If your application needs long transactions holding <code>SET<\/code> state, advisory locks, <code>LISTEN<\/code>\/<code>NOTIFY<\/code>, or prepared statements that must survive across requests, transaction pooling will fight you. This is not a Hyperdrive-specific flaw \u2014 it is inherent to every transaction-mode pooler, PgBouncer included \u2014 but you need to know it before you commit.<\/p>\n<p>And if your workload is batch work with queries running longer than 60 seconds \u2014 ETL, large backfills, heavy reporting \u2014 Hyperdrive will terminate them. That work belongs on a server talking to Postgres directly, not in a Worker. We have written more about the boundaries in <a href=\"https:\/\/www.cipher.co.th\/en\/blogs\/when-not-to-use-cloudflare-workers\/\">when not to use Cloudflare Workers<\/a>, and about how the runtimes compare in <a href=\"https:\/\/www.cipher.co.th\/en\/blogs\/cloudflare-workers-vs-aws-lambda\/\">Cloudflare Workers vs AWS Lambda<\/a>.<\/p>\n<h2>Summary<\/h2>\n<p>Hyperdrive does not magically make your Postgres faster. It removes the two structural reasons Workers cannot talk to a normal database \u2014 the 6-connection ceiling and the cost of connection setup \u2014 and adds a cache on top. That is enough to move compute to the edge without touching the database, which is exactly what most teams actually want.<\/p>\n<p>Cipher designs and implements systems on the Cloudflare Developer Platform for businesses in Thailand. If you have a PostgreSQL that works and you are trying to decide which parts of your compute should move to Workers, we can assess the architecture and tell you which parts are worth moving and which should stay where they are.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<h3>Does using Hyperdrive require migrating the database<\/h3>\n<p>No. Hyperdrive connects to the PostgreSQL or MySQL database you already run, whether that is RDS, Cloud SQL, Azure Database, or a server you manage yourself. You take your existing connection string, create a configuration, and bind it to your Worker. No schema changes and no dropped extensions.<\/p>\n<h3>How much does Hyperdrive cost<\/h3>\n<p>As of July 2026, Hyperdrive is included in both the Workers Free and Workers Paid plans. The Free plan caps database queries at 100,000 per day; the Paid plan does not limit query volume. Connection pooling and query caching are included with no additional charge, and Hyperdrive does not charge for egress.<\/p>\n<h3>Does the Hyperdrive cache return stale data after a write<\/h3>\n<p>Yes. Cloudflare documentation states that Hyperdrive does not purge or invalidate cached read results when your application writes to the database. The defaults are a max_age of 60 seconds and a stale_while_revalidate of 15 seconds. For authentication, sessions, permissions, and reads that immediately follow a write, create a second cache-disabled Hyperdrive configuration and bind it alongside the cached one.<\/p>\n<h3>Why is a query containing NOW() never cached<\/h3>\n<p>Hyperdrive only caches queries whose functions PostgreSQL classifies as IMMUTABLE. Functions such as NOW(), CURRENT_DATE, and RANDOM() are classified STABLE or VOLATILE and therefore make the query uncacheable. The workaround is to compute the value in application code and pass it in as a query parameter.<\/p>\n<h3>Does Hyperdrive actually reduce latency<\/h3>\n<p>It does, because connection setup happens near the Worker and the connection pool sits near the database. But if your Worker issues several sequential queries per request, you should also use Placement to run the Worker near the database. Cloudflare documents roughly 20-30 ms of latency per query from a distant region versus 1-3 ms when the Worker is placed nearby.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Quick answer: Hyperdrive is a connection pooler and query cache that sits between Cloudflare Workers and the PostgreSQL or MySQL [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_angie_page":false,"content-type":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"page_builder":"","footnotes":""},"categories":[152],"tags":[],"class_list":["post-18133","post","type-post","status-publish","format-standard","hentry","category-technology-en"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/posts\/18133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/comments?post=18133"}],"version-history":[{"count":0,"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/posts\/18133\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/media?parent=18133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/categories?post=18133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cipher.co.th\/en\/wp-json\/wp\/v2\/tags?post=18133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}