Skip to main content

Command Palette

Search for a command to run...

Building a HIPAA-Ready Architecture on AWS: Secure App Deployment with Private EC2, VPC Endpoints & ALB

Updated
7 min read
Building a HIPAA-Ready Architecture on AWS: Secure App Deployment with Private EC2, VPC Endpoints & ALB
S

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator

Healthcare systems deal with sensitive patient data, and if you are building or hosting an application for a U.S. healthcare provider, the architecture needs to meet the requirements of HIPAA (Health Insurance Portability and Accountability Act). HIPAA compliance in the cloud is not just about encryption, it's about designing infrastructure where data is secured both technically and logically, with the right audit controls and network boundaries.

In this guide, we will walk through how we built a HIPAA-aligned AWS environment to run a Python Flask API on an EC2 instance in a private subnet, exposed securely using an Application Load Balancer (ALB) without placing the EC2 directly on the public internet.

Here, the setup is manual, we can do the same using Terraform! Perhaps, in a different article, we shall explore that!


Signing the AWS Business Associate Addendum (BAA)

HIPAA requires that AWS legally commits to protecting PHI (Protected Health Information).
To do this you must enable the Business Associate Addendum:

How to Enable BAA

  1. Go to AWS Console → AWS Artifact

  2. Open Agreements

  3. Request the Business Associate Addendum

  4. Accept digitally

Without this, using AWS to store or process patient data is not HIPAA compliant, even if your infrastructure design is perfect.


Architecture Overview (What We're Building)

Key properties:

  • App servers are in private subnets → not reachable from internet

  • Load Balancer lives in public subnet and forwards traffic internally

  • NAT Gateway allows private instances to download packages, pull images, etc.

  • S3 / ECR / STS / Logs access does not touch public internet thanks to VPC Endpoints


Step 1: Create the VPC

We start with:

VPC CIDR: 10.0.0.0/16

This gives enough room for multiple subnets.


Step 2: Create Subnets

We create one public and one private subnet per AZ (for reliability).

Subnet NameCIDRTypeUse
public-subnet-110.0.1.0/24Public (us-1a)ALB, NAT Gateway
public-subnet-210.0.4.0/24Public (us-1b)ALB, NAT Gateway
private-subnet-110.0.2.0/24Private (us-1a)EC2 application

Step 3: Internet Gateway + NAT Gateway

Attach an Internet Gateway to allow outbound traffic.

Place the NAT Gateway in the public subnet.

This allows private instances to:

  • Pull packages

  • Download updates

  • Clone repo / get code from S3

But prevents inbound connections.


Step 4: Route Tables

Public Route Table

0.0.0.0/0 → Internet Gateway

We associate the public subnets to this.

Private Route Table

0.0.0.0/0 → NAT Gateway

We associate the private subnet to this.


Step 5: VPC Endpoints (Important for Compliance‑Style Security)

To avoid public internet paths for AWS services, create VPC Interface / Gateway Endpoints, and make sure they are at private subnets in case of interfaces and for Gateway is in the private route table:

TypeService Endpoint
S3Gatewaycom.amazonaws.[region].s3
EC2MessagesInterfacecom.amazonaws.[region].ec2messages
SSM MessagesInterfacecom.amazonaws.[region].ssmmessages
SSMInterfacecom.amazonaws.[region].ssm
BedrockInterfacecom.amazonaws.[region].bedrock-runtime

VPC → Endpoints → Create Endpoint

Make sure Enable DNS name is On, in all cases.

Endpoint #1 — SSM

FieldValue
Service Namecom.amazonaws.us-east-1.ssm
Service TypeInterface
VPChipaa-vpc
Subnetsprivate-subnet-hipaa-1 (select this)
Security GroupChoose or create a SG that ALLOWS HTTPS (443) from your private subnet

Click Create.


Endpoint #2 — EC2 Messages

FieldValue
Service Namecom.amazonaws.us-east-1.ec2messages
Service TypeInterface
VPChipaa-vpc
Subnetsprivate-subnet-hipaa-1
Security GroupSame SG as above

Click Create.


Endpoint #3 — SSM Messages

FieldValue
Service Namecom.amazonaws.us-east-1.ssmmessages
Service TypeInterface
VPChipaa-vpc
Subnetsprivate-subnet-hipaa-1
Security GroupSame SG

Click Create.


Endpoint #4 — S3 Gateway (Important)

FieldValue
Service Namecom.amazonaws.us-east-1.s3
Service TypeGateway
VPChipaa-vpc
Route TablesSELECT the Private Route Table (do not choose Public RT)

Click Create.

Endpoint #5 — Bedrock

FieldValue
Service Namecom.amazonaws.us-east-1.bedrock-runtime
Service TypeInterface
VPChipaa-vpc
Subnetsprivate-subnet-hipaa-1 (select this)
Security GroupUse the SG attached to your EC2, allow outbound HTTPS (default outbound all is fine)

Click Create.

Security Group Rule (IMPORTANT)

Go to Security Groups → Edit inbound rules of the SG used for the 3 interface endpoints:

Add Rule:

TypeProtocolPortSource
HTTPSTCP44310.0.0.0/16 (your VPC CIDR)

Step 6: Launch EC2 Instance in Private Subnet

  • AMI: Amazon Linux 2023 (HIPAA supported)

  • Subnet: Private (10.0.2.0/24)

  • No Public IP

  • Security Group Rules:

TypeSourcePurpose
Allow inbound: 5212From Target Group SGALB → EC2 only
OutboundAllRequired for package updates

We accessed the instance using:

AWS Systems Manager → Session Manager

(no SSH needed)

It is also important that we add appropriate IAM Role to the EC2 machine, to make sure it can do and perform actions like pulling from S3, logging, ECR pulling and so on.

Instance Role Policies Example:

AmazonS3ReadOnlyAccess
AmazonEC2ReadOnlyAccess
AmazonSSMManagedInstanceCore
AmazonBedrockFullAccess

Step 7: Deploying the Flask Application

We can upload our code as zip to a s3 bucket and pull it in our ec2, when we are using Session Manager.

aws s3 cp s3://your-bucket/simpleflask/ /home/ec2-user/simpleflask --recursive

Directory Structure

We extract the zip of our code that we pulled , and unzip it, we can see the following:

/app
  ├─ app.py
  └─ requirements.txt

Install Dependencies

sudo yum update -y
sudo yum install python3 git -y

Step 8: Run the Flask App

Firstly, we activate a virtual env and install the requirements:

python3 -m venv env
source env/bin/activate
pip install -r requirements.txt

App listens privately, example:

import json
import boto3
from fastapi import FastAPI

app = FastAPI()

client = boto3.client("bedrock-runtime", region_name="us-east-1")

@app.get("/")
def healthcheck():
    return {"status": "running (private, HIPAA-safe)"}

@app.post("/chat")
def chat(message: str):
    payload = {
        "messages": [
            {"role": "user", "content": message}
        ],
        "max_tokens": 200
    }

    response = client.invoke_model(
        modelId="mistral.mistral-large-2402-v1:0",
        contentType="application/json",
        accept="application/json",
        body=json.dumps(payload)
    )

    return json.loads(response["body"].read().decode())

app.run(host="0.0.0.0", port=5212)

Step 9: Running Flask with systemd

Create service file:

sudo nano /etc/systemd/system/flask.service
[Unit]
Description=Flask App Service
After=network.target

[Service]
User=ec2-user
WorkingDirectory=/app
ExecStart=/usr/bin/python3 /app/app.py
Restart=always

[Install]
WantedBy=multi-user.target

Activate:

sudo systemctl daemon-reload
sudo systemctl enable flask
sudo systemctl start flask

This will make sure that our app runs as long as the machine is alive.


Step 10: Target Group Setup

  • Target Type: Instances

  • Port: 5212

  • Health Check Path: /

Step 11: Create Application Load Balancer (ALB)

SettingValue
SchemeInternet-facing
TypeApplication Load Balancer
SubnetsPublic A & Public B
ListenerHTTP : 80 → Target Group

Security Group for ALB

TypeSource
HTTP (80)0.0.0.0/0

Security Group for EC2

TypeSource
TCP 5212SG of ALB

No direct EC2 exposure.


Step 12: Testing

curl http://<ALB-DNS>/

You should receive:

Hello from HIPAA-secure backend!

Final Outcome

You now have:

  • Private app servers with no inbound exposure

  • Load balancer securely fronting traffic

  • VPC Endpoints eliminating public bandwidth paths

  • NAT‑restricted outbound traffic only

This architecture is HIPAA‑friendly because:

  • No PHI touches public networks

  • All services are inside private routing boundaries

  • Access is strictly controlled and logged


Conclusion

We successfully deployed a HIPAA-aligned backend on AWS using:

  • Private compute networks

  • Secure load balancing

  • Controlled outbound-only access

  • VPC endpoint isolation

  • No public exposure of the EC2 instance

This architecture is a strong baseline for any healthcare SaaS, EHR integrations, or PHI-processing APIs.
We can also perform the same with API Gateway and VPC link setup!

PS: Make sure delete the Load Balancer, Target groups , and stop/terminate the EC2 instance when you are done with this, as it incur costs!

More from this blog

C

CodeOps Studies

39 posts

Simple write-ups on day to day code or devops experiments, tests etc.

Building a HIPAA-Ready Architecture on AWS