One of the 21 third-party apps I audited in June and July 2026 had an endpoint where a single web request drops the entire production database; disk encryption would not have stopped it. How to harden managed database settings comes down to five things the provider leaves to you: network access, the app’s role, TLS, object ownership and manual access.
How to harden managed database settings: the five the provider leaves to you
Hardening a managed database means changing five settings the provider leaves to the customer, by my count: restrict which networks can connect, give the app a role that is not a superuser and does not own the tables, make the server refuse unencrypted connections, keep ownership with a migration role, and make every human and agent credential read-only.
Those 21 were apps I audited in two groups: 11 public vibe-coded apps audited exhaustively across all 12 pillars, and 10 further third-party apps, disjoint from the first group, audited blind. The endpoint that drops the whole database with one request was in one app of the 21, so read it as one case, not a trend; the 21 are a selected set, not a random sample and not a rate for AI-built apps in general. The rest of the database side of launch, from migrations to backups, is mapped in the data consistency checklist for SaaS; this page covers the settings.
My reading of the provider pages behind the table in the next section: the provider runs the server and its storage, and who may connect, and as whom, stays with the customer. That split is why the table below has five rows and no row for encryption at rest.
| Setting | What to set | The check that fails when it is wrong |
|---|---|---|
| 1. Network | An allow list or private networking, so the database does not accept connections from anywhere | A connection from an address not on the list is refused |
| 2. The app’s role | A login that is not a superuser, does not bypass row-level security and does not own the tables | The role-attribute query returns false three times |
| 3. TLS | The server refuses unencrypted connections, and the client verifies the server’s certificate | A connection with sslmode=disable is refused |
| 4. Ownership | One migration role owns the tables, views and functions, and the app never logs in as it | The owner list shows no app role |
| 5. Manual access | People, dashboards and coding agents hold read-only credentials | The credential list shows each one read-only, or as the app or the migration job |
Printed as it is, with no download, the table is a template for database security: copy it into your runbook and write the date and result of each check beside its row. Of the database security strategies a small team can run, these five are the ones a managed host hands back to its customer, which is why I start with them. Database and application security meet at the app’s role: the database half is here, and the defaults on the application side are in what insecure defaults are.
One piece of common advice I leave out of the table. OpenSSF’s hardening post lists changing the default database port as “One common change” ; my view is that a new port cuts scan noise in the logs and is not a control I count.
Why it matters: the encryption you already have, and what it does not stop
Encryption at rest on a managed Postgres host is the provider’s job, and the table below shows what each host says it does by default. It covers a stolen disk or a copied snapshot. It does nothing against a connection that logs in, because the database hands decrypted rows to any role allowed to read them.
| Provider | At rest | In transit by default | Can the server require TLS? | Source and date read |
|---|---|---|---|---|
| Supabase | All customer data encrypted with AES-256 | Postgres accepts connections without SSL “to maximize client compatibility”; the HTTP APIs enforce SSL automatically | Yes: “Enforce SSL on incoming connections” in Database Settings | Supabase security page and SSL enforcement docs, 2026-09-28 |
| Neon | AES-256 on the NVMe storage volumes | ”Neon requires that all connections use SSL/TLS encryption” | Already required | Neon security overview, 2026-09-28 |
| Amazon RDS for PostgreSQL | AES-256 when encryption is chosen at creation; the RDS API reference says “By default, it isn’t encrypted” | rds.force_ssl defaults to 1 (on) for version 15 and later, 0 (off) for 14 and older | Yes: set rds.force_ssl to 1 in a custom DB parameter group | Amazon RDS encryption, SSL and CreateDBInstance API docs, 2026-09-28 |
| Cloud Firestore (Firebase apps) | Encrypted automatically before it is written to disk; Google Cloud’s default-encryption page names AES-256 for all data Google stores | TLS on reads and writes over the Internet | Not stated in Google’s docs | Firestore server-side encryption and Google Cloud default encryption pages, 2026-09-28 |
Each row comes from the provider’s own page: Supabase’s security page, Neon’s security overview, Amazon RDS encryption at rest and Firestore’s server-side encryption. On RDS the choice belongs to the day the instance is created; for an instance that started unencrypted, AWS’s route is an encrypted copy of a snapshot.
Encryption at rest in Postgres itself sits mostly outside the database. PostgreSQL’s encryption options list storage encryption “at the file system level or the block level” and the pgcrypto module for specific columns; no built-in transparent data encryption is among them. On a managed host, then, encryption at rest is the provider’s storage encryption. The same page says what that layer does not do: it “does not protect against attacks while the file system is mounted, because when mounted, the operating system provides an unencrypted view of the data.” The Supabase version of this question has its own answer in the FAQ of the credentials article linked in the next section.
In the same June and July 2026 group of 21 third-party apps, which I chose rather than sampled, the failures sat above the disk: at least 5 of the 21 exposed PII or PHI and 9 of the 21 had row-level security gaps. Across that group the Data Integrity & Safety pillar averages 51.6 out of 100, scored on 20 of the 21. The database-dropping endpoint from the opening line came from the same set. None of those is a problem a disk-encryption switch addresses.
If you need to encrypt sensitive data beyond the disk, that is a decision about specific columns: which ones, which keys, and who can decrypt. Sensitive data encryption at that level, and the definitions behind it, are covered in database encryption for a small SaaS. Two platform pages sit beside this one: whether Supabase is safe, platform and project, and what Google protects in Firebase and what you configure, which is the Firebase side of encrypting sensitive data.
Encryption at rest vs in transit on a managed database
Encryption at rest vs in transit is the difference between data sitting on the provider’s disks and data moving over a network. On a managed database the provider handles the first; the second is TLS on every connection, which the server can be set to require and the client can be set to verify.
What data encryption is, how it works and which types exist are defined in the database encryption article linked above, so this page does not repeat them. When a customer’s security questionnaire asks whether you encrypt at rest and in transit, the provider table answers the first half and the TLS section below answers the second. Read across its rows, that table is the encryption of data in transit and at rest for each host, as each provider documents it.
How it works: the settings, one by one
The first three parts below follow the table’s rows and end on the check that the next section collects; the fourth maps two of those settings to HIPAA’s encryption lines.
Who can connect, and which role the app uses
Database security for a small app turns on where a connection can come from and which role it logs in as. By my working rule, the app’s role needs a login and the table privileges it uses, and must not be a superuser, must not bypass row-level security and must not own the tables.
Database network security on the three Postgres hosts comes down to one setting each. Supabase network restrictions control which IP ranges can connect to Postgres and its pooler; until they are applied, “All IPs can connect”, and they do not cover the HTTPS APIs such as PostgREST, Storage and Auth. If direct connections to your project resolve to an IPv6 address, Supabase says to add both IPv4 and IPv6 ranges to the allow list. Neon’s IP Allow is “available with the Neon Scale plan” , so on Neon’s other plans row 1 has no allow list to set, and in my reading the app’s role carries the weight. On Amazon RDS, the Public access option decides whether the instance gets a public IP address at all, and “Access to the DB instance is ultimately controlled by the security group it uses.”
Serverless hosts whose outbound addresses change make allow lists hard to keep. My fallback there is a pooler in front of the database and a tightly limited app role, and the pooling side is a separate topic: connection pooling in Postgres.
The role side is where I would spend the time. My working rule is three roles, each with one job:
| Role | What it is for | What it must not have | Who holds its password |
|---|---|---|---|
| Migration role | Owns the tables, views and functions; runs schema changes | A place in the app’s runtime connection string, or use for ad-hoc queries | The migration job only |
| App role | The login the app’s server code connects with, holding only the table privileges it uses | SUPERUSER, BYPASSRLS, CREATEROLE, or ownership of any table | The app’s runtime environment |
| Read-only role | People, dashboards and agents that need to look at data | Insert, update, delete or schema-change rights | Named people and tools, each with its own login |
In PostgreSQL, a table’s owner normally bypasses row security, and PostgreSQL’s row security policies add that an owner “can choose to be subject to row security with ALTER TABLE … FORCE ROW LEVEL SECURITY”. Unless the table is set that way, an app that logs in as the owner skips the policies written to protect that table, which is why the app never logs in as the owner.
Ownership reaches views too. In two apps I audited, views created without security_invoker skipped row-level security: in a B2B starter, a pending-membership view joining members, profiles and the auth users table was readable by every signed-in user, exposing the name, email, requested organization and role of everyone who ever asked to join any tenant. In a food-delivery app, a chat-status view exposed every delivery chat’s participants and phone numbers to anonymous callers. The mechanism is in PostgreSQL’s CREATE VIEW reference: when a base table has row-level security, “by default, the row-level security policies of the view owner are applied”. The fix, and why views run as their creator unless you say otherwise, is in the RLS practices article.
On Supabase the matching mistake is server code using the service_role key where the signed-in user’s token would do. Current secret and legacy service_role keys are elevated server credentials that bypass RLS; the secret key runs as service_role, which carries the BYPASSRLS attribute. The full list of what someone can do with each Supabase credential is in its own article.
Write the rules down. A database access control policy for a small team fits in six lines, and this is my working rule for what they say:
- The roles that exist and what each is for.
- Who holds each credential, by name.
- How access is granted, and how it is removed when someone leaves.
- That production access is read-only by default.
- Where credentials are stored.
- The date of the next review.
The app role, created with no special attributes and granted only what it uses on one schema, looks like this. The syntax follows PostgreSQL’s CREATE ROLE and GRANT pages; the table names are placeholders for your own.
create role app_runtime login password 'replace-with-a-generated-secret'
nosuperuser nobypassrls nocreaterole;
grant usage on schema public to app_runtime;
grant select, insert, update, delete on table public.orders, public.customers to app_runtime;
The check for this section is the role-attribute query in check 4 below, run while connected as app_runtime.
How to secure data in transit: enforce TLS from the app to the database
Securing data in transit to a database takes a setting on each side: the server refuses connections without TLS, and the client uses sslmode=verify-full so it checks the server’s certificate. The common sslmode=require encrypts the traffic and, on its own, does not confirm who is on the other end.
Data in transit is data moving between two machines: browser to app, app to database, app to a model provider. The common protocol for encrypting data in transit is TLS, and for the database hop there are two sides to set.
The server side is a switch on each host. On Supabase it is Supabase SSL enforcement, the “Enforce SSL on incoming connections” setting, and changing it needs a brief database reboot. On Amazon RDS it is the rds.force_ssl parameter from RDS for PostgreSQL SSL settings, on by default from version 15 and off by default on 14 and older, so an older instance needs a custom parameter group. Neon already requires TLS on every connection.
The client side is the sslmode in the connection string, and libpq’s sslmode table says what each value protects against:
| sslmode value | Encrypts | Verifies the server | Use it when |
|---|---|---|---|
disable | No | No | Never, outside a local test |
allow, prefer | Maybe | No | Never on purpose; prefer is the default, which the docs call not recommended in secure deployments |
require | Yes | No | Only when you cannot get the CA certificate yet |
verify-ca | Yes | Depends on CA policy | A private or self-signed CA |
verify-full | Yes | Yes | Production, with the provider’s CA certificate |
The point most connection strings miss is the gap between require and verify-full. The docs’ own statement for require ends “I trust that the network will make sure I always connect to the server I want”, while verify-full adds “I want to be sure that I connect to a server I trust, and that it’s the one I specify.” There is one condition: if a root CA file exists on the client, require behaves like verify-ca for backwards compatibility, and the docs say “Relying on this behavior is discouraged”. Supabase and Neon both document verify-full; on Supabase the CA certificate is downloaded from the SSL Configuration section of Database Settings.
What happens if encrypted data is altered during transmission is written into RFC 8446, the TLS 1.3 standard: “If the decryption fails, the receiver MUST terminate the connection with a “bad_record_mac” alert.” In practice, as I read it, the app sees a failed query rather than altered rows, since the connection it was using is gone.
Encryption protocols for data in transit are versioned, and for TLS the last setting is which versions to allow. NIST SP 800-52 Revision 2 requires that TLS 1.2 configured with FIPS-based cipher suites be supported by all government TLS servers and clients, and requires support for TLS 1.3 by January 1, 2024. It is written for government systems; for a private app it makes a sensible floor to compare a provider’s supported versions against.
Protecting data in transit from the browser to the app is a separate hop with its own setup, covered in add HTTPS to a website. Among data in transit encryption methods for the database hop, TLS with verify-full is the one to set, and one detail is easy to miss: a TLS-terminating pooler in between means two hops to check, the app to the pooler and the pooler to the database. The check for this section is two connections, one with sslmode=disable that must fail and one with verify-full that must succeed.
Nobody should be running ad-hoc queries against production, including an agent
A production database is the live one holding real customers’ rows, so my working rule gives it read-write credentials in exactly two places: the app and the migration job. Every person, dashboard and coding agent gets a read-only role, pointed at staging by default, and changes reach production as reviewed migrations, never as a query typed into a console.
My working rule for who holds what:
| Who or what connects | Credential it should hold | What it must not be able to do |
|---|---|---|
| Developer laptop | The read-only role | Write to or change the schema of production |
| SQL dashboard or BI tool | The read-only role, on its own login | Share a login with a person or with the app |
| Coding agent or MCP server | Read-only, pointed at staging by default | Reach production with write access, or reach other projects |
| Support scripts | The app’s own admin actions, not raw SQL | Run a query no one reviewed against production |
For the agent row, Supabase’s MCP server has both controls: a read_only=true option to “Execute all queries as a read-only Postgres user”, and a project_ref option to scope it to one project, which “prevents LLMs from accessing data from other projects in your Supabase account”. Supabase’s MCP server documentation also tells you to scope the server and enable read-only mode before connecting a production project.
These are Postgres roles, and the host’s dashboard is a separate way in with its own permissions. On Supabase, the Read-Only member role is only available on the Team and Enterprise plans, and in the access-control table it is the one role whose SQL Editor runs carry the note “Limited to executing SELECT queries.”; Owner, Admin and Developer runs carry no such note. As I read that table, on the other plans dashboard access is write access, so it goes only to the people your policy lets write.
Taking back the standing write access an AI agent should lose, across every tool it can reach, has its own article. So does when an AI agent deleted the production database, including the first hour after. The rules that live in the repository are guardrails for AI coding agents. Changes reach production as migrations; the case for a separate staging database is made in One Database, No Staging: A Deploy Set to Break. The check for this section is the credential list in check 7.
HIPAA encryption requirements, and the two settings they land on
HIPAA encryption requirements, for a database on a managed host, touch two settings this page already covers: the provider’s encryption of stored data and TLS on every connection. The rule’s technical safeguards section, 45 CFR 164.312, names no algorithm, and whether HIPAA reaches an app at all is a separate question worth settling first.
HIPAA is US federal law, and whether it applies to your app, what its encryption lines say and what “addressable” means are read in full in what HIPAA’s Security Rule actually names; this page does not repeat that reading. What this page adds is where the lines land. The section’s text, 45 CFR 164.312, states no cipher or key length, so there is no single HIPAA encryption standard to pick from it. On the HIPAA requirements for data storage, my reading is that the rule’s encryption line for stored data is the one the provider’s at-rest encryption speaks to, and its transmission security standard is the one TLS enforcement speaks to. What documentation the rule makes you keep, and for how long, is answered in the policies question of that article’s FAQ.
Neither setting is a HIPAA-compliant encryption switch, and together they do not make an app compliant. Encryption for HIPAA compliance may need more than this, such as column-level encryption or key separation, and that is a risk-analysis decision worked through in whether Supabase is HIPAA compliant and what the BAA covers. On Supabase, SSL Enforcement and Network Restrictions, the two settings above, are among what High Compliance currently requires. This is not legal advice.
How to check your own database
A managed database is checked with seven tests: a connection from outside the allow list, a connection without TLS, a verified TLS connection, the app role’s attributes, schema changes as the app’s role, the table owners, and the list of everyone holding a production credential.
Run them against a database you own, the schema tests on a staging copy, and keep the evidence each one names. The checks and the evidence are my working rule.
- 01 Connect from a network not on the allow list, such as a phone hotspot, and expect a refusal or a timeout. Count a timeout as a pass only after the same client and connection string connect from an address on the list. On Supabase, use the pooler connection string for both runs, because the shared pooler is IPv4-only on every plan while a direct connection may resolve to IPv6, which an IPv4-only network cannot reach. Evidence: the error text and time, and the allowed run.
- 02 Connect with sslmode=disable and expect the server to refuse. Evidence: the error.
- 03 Connect with sslmode=verify-full and the provider CA certificate, and expect success. Evidence: the connection line.
- 04 As the app role, run the role-attribute query below and expect three false values. Evidence: the output.
- 05 As the app role, on a staging copy, try create table on a scratch name and expect permission denied. On a database running PostgreSQL 14 or earlier, or upgraded from one, every role can create in the public schema until REVOKE CREATE ON SCHEMA public FROM PUBLIC; is run, so run that first or this check fails a correctly limited role. Then try drop table on an existing app table and expect a refusal, since only the table owner, the schema owner and a superuser can drop a table. Evidence: both errors.
- 06 List the table owners and confirm none is the app role. Evidence: the list.
- 07 List every person, tool and agent holding a production credential and confirm each is read-only, or is the app or the migration job. Evidence: the list, dated.
The drop-table step uses a table that exists on purpose: a scratch table that was never created would fail for the wrong reason. The query in check 4 reads the rolsuper, rolbypassrls and rolcreaterole columns of pg_roles, all documented in PostgreSQL’s reference, and for check 6 the tableowner column of pg_tables lists the owners.
select rolsuper, rolbypassrls, rolcreaterole from pg_roles where rolname = current_user;
In the Production Hardening Sprint, deliverable 2.9, managed-platform security settings, is verified this way: record the before-and-after settings for each managed service, naming each risky default and its new value.
Three neighboring checks live elsewhere: backups in the database backup checklist for startups, buckets in the object storage security checklist, and the plan that lists these settings as recovery facts in a business continuity plan template.
Where the sprint fits
Three more deliverables in the sprint sit next to 2.9, managed-platform security settings. Deliverable 2.3, server-only administrative keys, is verified this way: check runtime configuration, built assets, and each code path using administrative credentials. On the data side, deliverable 1.4, data isolation across every table, is verified this way: run read and write tests as anonymous users, different roles, and separate tenants. Deliverable 2.4, environment configuration separation, is verified this way: inspect environment mappings and confirm staging actions stay within staging services. Formal third-party certifications and independent audit opinions are separate from the sprint deliverables. Legal advice and certification are separate services; the sprint implements and documents the technical data-handling controls. Third-party hosting, service subscriptions and API consumption remain in the client’s own accounts, and required service costs are explained before anything is enabled. Each deliverable and its check is listed in the published scope.
Common questions about managed database security and encryption
Does TLS 1.2 encrypt data in transit?
Yes. TLS 1.2 is one of the versions NIST SP 800-52 Revision 2 requires all government TLS servers and clients to support, configured with FIPS-based cipher suites, alongside support for TLS 1.3 by January 1, 2024. For a database connection the version matters less than the client’s sslmode: an encrypted connection that never checks the server’s certificate can still be talking to the wrong server.
Is AES in transit or at rest?
Both, in different layers. Supabase, Neon and Amazon RDS all name AES-256 for data at rest. In transit, TLS 1.3 requires every compliant application to implement the TLS_AES_128_GCM_SHA256 cipher suite, so whether a given session uses AES depends on the cipher suite the two ends agree on.
Can hackers access encrypted data?
Usually by logging in, not by breaking the cipher, as I read the cases. Encrypted storage is read through the database, and the database serves plain rows to any login whose role may read them, so a leaked key, an over-privileged role or an open endpoint is the usual route. The endpoint from the opening line of this page, one request that drops the whole database, is that kind of route.
What are the downsides of encrypting data?
Two matter for a small team, in my reading. Lose the key and you lose the data, and a column encrypted with its own key is much harder to filter or sort on, because the database mostly sees ciphertext. Disk encryption on a managed host has neither cost for you, which is why the real decision is which columns, if any, to encrypt on top; the database encryption article covers that choice.
Should I turn on data encryption?
On Supabase, Neon and Firestore the disk side is already on, as the provider table shows, so there is nothing to switch. On Amazon RDS it is a choice made once: “You can only encrypt an Amazon RDS DB instance when you create it, not after the DB instance is created,” so confirm an existing instance has it. The switch that can still be off is TLS enforcement: on Supabase until you turn it on, and on RDS instances running version 14 or older, where it starts off.
The checks in this guide show you where the app is open. The sprint below closes those gaps, tests the result and writes the evidence down.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase