Locking Down AWS: Private Networks, No Public Databases, and Deployments Without Keys

Most cloud breaches are not clever. They are a database left reachable from the internet, or an access key committed to a repository. Here is how we structure AWS so that a mistake in one place does not become a breach everywhere.

When a cloud breach makes the news, the cause is rarely sophisticated. It is usually a database that accepted connections from anywhere, a storage bucket set to public, or a long-lived access key that ended up in a repository or a laptop backup. The attack was ordinary. The configuration made it possible.

This is the second article in our series on securing web applications. The first covered stopping traffic at the edge before it reaches your application. This one is about the layer underneath: how the infrastructure is arranged, so that a problem in one component does not hand over everything else.

Separate your networks, and keep most things off the internet

The default instinct when setting up a server is to give it a public address so you can reach it. It works immediately, which is the problem. Every component with a public address is a component the entire internet can attempt to connect to.

A better structure splits the network into public and private sections. The public section holds only what genuinely needs to face the world, typically a load balancer. Everything else, application servers, databases, caches, background workers, sits in the private section with no route from the internet at all.

Traffic arrives at the load balancer, which forwards it inward. A request cannot reach an application server directly, because there is no path. An attacker who learns the internal address of your database gains nothing, because nothing outside the network can open a connection to it.

We keep environments separated the same way. Production, staging and UAT each get their own network boundary, so a misconfiguration while testing cannot reach production data, and a compromised test environment is not a route into the live system.

Your database should have no public route. None.

This deserves stating plainly because it is still common to find otherwise. A managed database should sit in the private network, with public accessibility switched off, reachable only from the application servers that need it.

The objection is always about access. Developers need to run queries, analysts need to pull reports, someone needs to inspect a problem at 2am. All of that is solvable without a public endpoint: a bastion host that is itself locked down, a session manager that tunnels through the cloud provider, or a VPN into the private network.

Each of those requires authentication you control and produces a log of who connected. A public database endpoint protected by a password offers neither.

Security groups: allow by identity, not by address range

Security groups are the firewall rules around each component. The usual mistake is to write them in terms of IP ranges, which drift as infrastructure changes and tend to get widened under deadline pressure until something is open far more broadly than anyone intended.

A cleaner approach is to write rules that reference other security groups. The database accepts connections on its port from the application security group, not from a list of addresses. Any server in that group can connect, and any server outside it cannot, regardless of where it sits or what address it has.

The rules then describe intent rather than topology: this tier may talk to that tier, on this port, and nothing else. When you add a server to the application tier, access follows automatically. When you remove one, it stops. Nobody needs to remember to update a list.

Stop deploying with API keys

This is the change with the biggest effect, and the one most often left undone.

A long-lived access key is a username and password for your cloud account, usually with broad permissions, sitting in a CI system, a configuration file, a developer laptop, and quite often in a repository where someone pasted it to fix an urgent problem. It does not expire. It works from anywhere. If it leaks, the finder has whatever it can do, and you will probably not know.

Published research on cloud incidents keeps finding the same pattern: a leaked credential, discovered by automated scanning of public repositories within minutes of being committed, then used before anyone noticed.

The alternative is to use roles. A deployment pipeline assumes a role scoped to exactly what deploying requires. The credentials it receives are generated at the moment of use and expire shortly after. An application server gets its permissions from an instance role instead of a key in a config file. There is no long-lived secret to leak, because there is no long-lived secret.

Where a credential genuinely must exist, such as a third-party API key, it belongs in a managed secret store, retrieved at runtime by a service that is allowed to read it, never committed to a repository and never pasted into an environment file that gets copied around.

Give every component the least it needs

It is quicker to grant broad permissions and move on. It is also how a small compromise becomes a large one. A service that only needs to read from one storage location should be able to do exactly that, not read and write everything in the account.

The honest way to approach this is to start restrictive and widen when something legitimately breaks. Starting broad with the intention of tightening later almost never gets revisited, because nothing is visibly wrong.

What this buys you

Arranged this way, the common failures stop being catastrophic:

  • A vulnerability in your application exposes the application, not the database directly, because the attacker still cannot open a connection to anything else.
  • A leaked credential from a pipeline expires, and was only ever scoped to deployment.
  • A compromised test environment stays in the test environment.
  • Someone scanning for public databases finds nothing to scan.

None of this requires exotic tooling. It is the standard set of controls every major cloud provides, arranged deliberately rather than left at whatever default got you running fastest.

Next

Good structure reduces how much can go wrong. It does not tell you when something has. The final article in this series covers the detection side: threat monitoring, malware scanning, flow logs and the habits in code that keep the whole arrangement standing.

Logic Providers designs and runs AWS infrastructure for client platforms. If you are unsure how your own environment is arranged, we are glad to take a look.

Share This Article

Tags

Security AWS Cloud VPC IAM DevOps
Mukta Gautam
About the Author
Mukta Gautam
Connect on LinkedIn
Written by
Mukta Gautam
Mukta Gautam
LinkedIn
Published
October 31, 2026
Read Time
10 min read
Category
Security
Tags
Security AWS Cloud VPC IAM DevOps
Start Your Project

Related Articles

Have a Project in Mind?

Let's discuss how we can help bring your vision to life.