Retour au blog
4 février 202621 min read

HIPAA-Compliant AWS Architecture: Reference Design for Healthcare Applications

Comprehensive guide to building HIPAA-compliant applications on AWS. Learn the reference architecture, required services, encryption strategies, and Terraform implementation for healthcare workloads.

HIPAA AWS architectureHIPAA compliant cloud infrastructureAWS healthcare complianceHIPAA BAA AWS serviceshealthcare cloud security
Share:

Healthcare organizations moving to the cloud face a critical question: How do we build on AWS while maintaining HIPAA compliance?

The answer isn't as simple as "use these services and check these boxes." HIPAA compliance on AWS requires a carefully architected solution that addresses encryption, access controls, audit logging, disaster recovery, and Business Associate Agreements—all while maintaining the performance and scalability benefits of cloud infrastructure.

This comprehensive guide provides a reference architecture for HIPAA-compliant healthcare applications on AWS, complete with service selection guidance, security configurations, and production-ready Terraform code you can adapt for your organization.

HIPAA Compliance Fundamentals on AWS

The Shared Responsibility Model

AWS operates under a shared responsibility model for HIPAA compliance:

AWS Responsibilities (Security OF the Cloud):

  • Physical security of data centers
  • Hardware and network infrastructure
  • Hypervisor and virtualization layer security
  • Managed service security (RDS, S3, etc.)
  • HIPAA-eligible service compliance certifications

Your Responsibilities (Security IN the Cloud):

  • Data encryption (at rest and in transit)
  • Access controls and identity management
  • Network security (VPCs, security groups, NACLs)
  • Application-level security
  • Audit logging and monitoring
  • Incident response procedures
  • Business Associate Agreement (BAA) with AWS

Critical Point: AWS provides HIPAA-eligible services, but YOU must configure them correctly to achieve HIPAA compliance.

AWS Business Associate Agreement (BAA)

Before storing, processing, or transmitting PHI on AWS, you must execute a Business Associate Agreement with AWS. This is a legal requirement under HIPAA.

How to Sign AWS BAA:

  1. Log into AWS Artifact (via AWS Console → Compliance → Artifact)
  2. Review and accept the AWS BAA
  3. Download executed BAA for your compliance records
  4. Ensure all accounts storing PHI have signed BAAs

HIPAA-Eligible AWS Services (as of 2026):

  • Compute: EC2, ECS, EKS, Lambda, Batch
  • Storage: S3, EBS, EFS, FSx
  • Database: RDS (PostgreSQL, MySQL, Oracle, SQL Server), DynamoDB, Aurora, DocumentDB, Neptune, ElastiCache (Redis), MemoryDB for Redis
  • Networking: VPC, CloudFront, Route 53, Direct Connect, PrivateLink
  • Security: KMS, Secrets Manager, Certificate Manager, CloudHSM, WAF, Shield
  • Analytics: Athena, EMR, Kinesis, Glue, Redshift
  • Machine Learning: SageMaker
  • Monitoring: CloudWatch, CloudTrail, Config, GuardDuty, Security Hub
  • Management: Systems Manager, CloudFormation

NOT HIPAA-Eligible (do not use for PHI):

  • Lightsail, Elastic Beanstalk (limited support), WorkSpaces, AppStream, Comprehend Medical (unless BAA coverage confirmed), most AI/ML services without explicit BAA coverage

HIPAA Security Rule Requirements

Your AWS architecture must implement technical safeguards from HIPAA Security Rule:

HIPAA RequirementAWS Implementation
Access Control (§164.312(a)(1))IAM policies, MFA, role-based access, least privilege
Audit Controls (§164.312(b))CloudTrail, VPC Flow Logs, S3 access logs, application logs
Integrity (§164.312(c)(1))S3 versioning, RDS snapshots, checksums, object lock
Person/Entity Authentication (§164.312(d))IAM, Cognito, SSO integration, MFA enforcement
Transmission Security (§164.312(e)(1))TLS 1.2+, VPN, Direct Connect, PrivateLink
Encryption (§164.312(a)(2)(iv))KMS encryption at rest, TLS in transit, field-level encryption

Reference Architecture: HIPAA-Compliant Healthcare Application

High-Level Architecture Diagram

┌─────────────────────────────────────────────────────────────────────────┐
│                           INTERNET                                       │
└────────────────────────────────┬────────────────────────────────────────┘
                                 │
                                 ▼
                    ┌────────────────────────┐
                    │   Amazon Route 53      │
                    │   (DNS + Health Checks)│
                    └────────────┬───────────┘
                                 │
                                 ▼
                    ┌────────────────────────┐
                    │   AWS WAF              │
                    │   (Web Application     │
                    │    Firewall)           │
                    └────────────┬───────────┘
                                 │
                                 ▼
┌────────────────────────────────────────────────────────────────────────┐
│                     AWS ACCOUNT (PRODUCTION)                            │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                  Amazon CloudFront                                │  │
│  │  (CDN with TLS 1.2+, Field-level encryption)                     │  │
│  └────────────────────────────┬─────────────────────────────────────┘  │
│                                │                                        │
│                                ▼                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                    VPC (10.0.0.0/16)                              │  │
│  │                                                                   │  │
│  │  ┌────────────────────────────────────────────────────────────┐  │  │
│  │  │  PUBLIC SUBNETS (10.0.1.0/24, 10.0.2.0/24)                 │  │  │
│  │  │  - NAT Gateways (for private subnet internet access)       │  │  │
│  │  │  - Application Load Balancer                               │  │  │
│  │  │  - Bastion Host (locked down, session logging)             │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  │                                                                   │  │
│  │  ┌────────────────────────────────────────────────────────────┐  │  │
│  │  │  PRIVATE SUBNETS - APPLICATION TIER                        │  │  │
│  │  │  (10.0.10.0/24, 10.0.11.0/24)                              │  │  │
│  │  │                                                             │  │  │
│  │  │  ┌──────────────────┐      ┌──────────────────┐            │  │  │
│  │  │  │  ECS Fargate     │      │  ECS Fargate     │            │  │  │
│  │  │  │  Healthcare API  │      │  Healthcare API  │            │  │  │
│  │  │  │  (Container)     │      │  (Container)     │            │  │  │
│  │  │  └──────────────────┘      └──────────────────┘            │  │  │
│  │  │           ↓                         ↓                       │  │  │
│  │  │  ┌─────────────────────────────────────────────┐            │  │  │
│  │  │  │  ElastiCache Redis (Encrypted)              │            │  │  │
│  │  │  │  - Session storage (no PHI)                 │            │  │  │
│  │  │  │  - Rate limiting cache                      │            │  │  │
│  │  │  └─────────────────────────────────────────────┘            │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  │                                                                   │  │
│  │  ┌────────────────────────────────────────────────────────────┐  │  │
│  │  │  PRIVATE SUBNETS - DATABASE TIER                           │  │  │
│  │  │  (10.0.20.0/24, 10.0.21.0/24)                              │  │  │
│  │  │                                                             │  │  │
│  │  │  ┌─────────────────────────────────────────────┐            │  │  │
│  │  │  │  Amazon RDS PostgreSQL (Multi-AZ)           │            │  │  │
│  │  │  │  - Encrypted with KMS                       │            │  │  │
│  │  │  │  - Automated backups (7-year retention)     │            │  │  │
│  │  │  │  - Read replicas in separate AZ             │            │  │  │
│  │  │  └─────────────────────────────────────────────┘            │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  │                                                                   │  │
│  │  ┌────────────────────────────────────────────────────────────┐  │  │
│  │  │  SECURITY & MONITORING                                     │  │  │
│  │  │  - VPC Flow Logs → CloudWatch Logs                         │  │  │
│  │  │  - GuardDuty (threat detection)                            │  │  │
│  │  │  - Security Hub (compliance dashboards)                    │  │  │
│  │  │  - Config (resource compliance monitoring)                 │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  SUPPORTING SERVICES                                              │  │
│  │  - S3 Buckets (PHI storage, encrypted, versioned, lifecycle)     │  │
│  │  - KMS (encryption keys with automatic rotation)                 │  │
│  │  - Secrets Manager (database credentials, API keys)              │  │
│  │  - CloudTrail (all API calls logged, immutable)                  │  │
│  │  - CloudWatch Alarms (security + performance monitoring)         │  │
│  │  - SNS Topics (alert notifications to security team)             │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  DISASTER RECOVERY (DR) - SEPARATE REGION                         │  │
│  │  - RDS cross-region automated backups                             │  │
│  │  - S3 cross-region replication (PHI buckets)                      │  │
│  │  - Infrastructure as Code (Terraform) for rapid rebuild           │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────────┘

Architecture Principles

1. Defense in Depth: Multiple layers of security (WAF → ALB → Security Groups → NACLs → IAM → Encryption)

2. Least Privilege Access: Every resource has minimum required permissions (IAM roles, security groups)

3. Encryption Everywhere: Data encrypted at rest (KMS) and in transit (TLS 1.2+)

4. Immutable Audit Trail: All actions logged to CloudTrail, logs stored in immutable S3 buckets

5. High Availability: Multi-AZ deployment for zero downtime during maintenance or failures

6. Disaster Recovery: Cross-region backups, RTO < 4 hours, RPO < 1 hour

Network Architecture: VPC Design

VPC Configuration

# terraform/network.tf

# VPC with DNS support (required for RDS, PrivateLink)
resource "aws_vpc" "healthcare" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = {
    Name        = "healthcare-vpc-${var.environment}"
    HIPAA       = "true"
    Environment = var.environment
  }
}

# Internet Gateway (for public subnets)
resource "aws_internet_gateway" "healthcare" {
  vpc_id = aws_vpc.healthcare.id
  
  tags = {
    Name = "healthcare-igw-${var.environment}"
  }
}

# Public Subnets (for ALB, NAT Gateway, Bastion)
resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.healthcare.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  
  tags = {
    Name = "healthcare-public-subnet-${count.index + 1}-${var.environment}"
    Tier = "Public"
  }
}

# NAT Gateways (one per AZ for high availability)
resource "aws_eip" "nat" {
  count  = 2
  domain = "vpc"
  
  tags = {
    Name = "healthcare-nat-eip-${count.index + 1}-${var.environment}"
  }
}

resource "aws_nat_gateway" "healthcare" {
  count         = 2
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
  
  tags = {
    Name = "healthcare-nat-gw-${count.index + 1}-${var.environment}"
  }
  
  depends_on = [aws_internet_gateway.healthcare]
}

# Private Subnets - Application Tier
resource "aws_subnet" "private_app" {
  count             = 2
  vpc_id            = aws_vpc.healthcare.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
  
  tags = {
    Name = "healthcare-private-app-subnet-${count.index + 1}-${var.environment}"
    Tier = "Application"
  }
}

# Private Subnets - Database Tier (isolated from app tier)
resource "aws_subnet" "private_db" {
  count             = 2
  vpc_id            = aws_vpc.healthcare.id
  cidr_block        = "10.0.${count.index + 20}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
  
  tags = {
    Name        = "healthcare-private-db-subnet-${count.index + 1}-${var.environment}"
    Tier        = "Database"
    HIPAA       = "true"
    DataClass   = "PHI"
  }
}

# Route Table - Public Subnets
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.healthcare.id
  
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.healthcare.id
  }
  
  tags = {
    Name = "healthcare-public-rt-${var.environment}"
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Route Tables - Private App Subnets (route through NAT Gateway)
resource "aws_route_table" "private_app" {
  count  = 2
  vpc_id = aws_vpc.healthcare.id
  
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.healthcare[count.index].id
  }
  
  tags = {
    Name = "healthcare-private-app-rt-${count.index + 1}-${var.environment}"
  }
}

resource "aws_route_table_association" "private_app" {
  count          = 2
  subnet_id      = aws_subnet.private_app[count.index].id
  route_table_id = aws_route_table.private_app[count.index].id
}

# Route Tables - Private DB Subnets (NO internet access)
resource "aws_route_table" "private_db" {
  vpc_id = aws_vpc.healthcare.id
  
  # NO default route - database tier has no internet access
  
  tags = {
    Name = "healthcare-private-db-rt-${var.environment}"
  }
}

resource "aws_route_table_association" "private_db" {
  count          = 2
  subnet_id      = aws_subnet.private_db[count.index].id
  route_table_id = aws_route_table.private_db.id
}

# VPC Flow Logs (HIPAA audit requirement)
resource "aws_flow_log" "healthcare" {
  iam_role_arn    = aws_iam_role.vpc_flow_logs.arn
  log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.healthcare.id
  
  tags = {
    Name  = "healthcare-vpc-flow-logs-${var.environment}"
    HIPAA = "true"
  }
}

resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
  name              = "/aws/vpc/healthcare-${var.environment}"
  retention_in_days = 2555  # 7 years (HIPAA retention requirement)
  kms_key_id        = aws_kms_key.logs.arn
  
  tags = {
    HIPAA = "true"
  }
}

# VPC Endpoints (avoid internet for AWS service access)
resource "aws_vpc_endpoint" "s3" {
  vpc_id       = aws_vpc.healthcare.id
  service_name = "com.amazonaws.${var.aws_region}.s3"
  
  route_table_ids = concat(
    aws_route_table.private_app[*].id,
    [aws_route_table.private_db.id]
  )
  
  tags = {
    Name = "healthcare-s3-endpoint-${var.environment}"
  }
}

resource "aws_vpc_endpoint" "secrets_manager" {
  vpc_id              = aws_vpc.healthcare.id
  service_name        = "com.amazonaws.${var.aws_region}.secretsmanager"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = aws_subnet.private_app[*].id
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
  
  tags = {
    Name = "healthcare-secretsmanager-endpoint-${var.environment}"
  }
}

Security Groups (Principle of Least Privilege)

# terraform/security-groups.tf

# ALB Security Group (public-facing)
resource "aws_security_group" "alb" {
  name        = "healthcare-alb-${var.environment}"
  description = "Security group for Application Load Balancer"
  vpc_id      = aws_vpc.healthcare.id
  
  # Inbound HTTPS only
  ingress {
    description = "HTTPS from internet"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # Outbound to ECS containers only
  egress {
    description     = "To ECS containers"
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.ecs_tasks.id]
  }
  
  tags = {
    Name = "healthcare-alb-sg-${var.environment}"
  }
}

# ECS Tasks Security Group
resource "aws_security_group" "ecs_tasks" {
  name        = "healthcare-ecs-tasks-${var.environment}"
  description = "Security group for ECS tasks"
  vpc_id      = aws_vpc.healthcare.id
  
  # Inbound from ALB only
  ingress {
    description     = "From ALB"
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }
  
  # Outbound to RDS
  egress {
    description     = "To RDS"
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.rds.id]
  }
  
  # Outbound to ElastiCache
  egress {
    description     = "To ElastiCache Redis"
    from_port       = 6379
    to_port         = 6379
    protocol        = "tcp"
    security_groups = [aws_security_group.elasticache.id]
  }
  
  # Outbound HTTPS for AWS API calls (via VPC endpoints)
  egress {
    description = "To AWS services via VPC endpoints"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.healthcare.cidr_block]
  }
  
  tags = {
    Name = "healthcare-ecs-tasks-sg-${var.environment}"
  }
}

# RDS Security Group
resource "aws_security_group" "rds" {
  name        = "healthcare-rds-${var.environment}"
  description = "Security group for RDS PostgreSQL"
  vpc_id      = aws_vpc.healthcare.id
  
  # Inbound from ECS tasks only
  ingress {
    description     = "PostgreSQL from ECS tasks"
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.ecs_tasks.id]
  }
  
  # NO outbound rules (database doesn't need to initiate connections)
  
  tags = {
    Name      = "healthcare-rds-sg-${var.environment}"
    HIPAA     = "true"
    DataClass = "PHI"
  }
}

# ElastiCache Redis Security Group
resource "aws_security_group" "elasticache" {
  name        = "healthcare-elasticache-${var.environment}"
  description = "Security group for ElastiCache Redis"
  vpc_id      = aws_vpc.healthcare.id
  
  # Inbound from ECS tasks only
  ingress {
    description     = "Redis from ECS tasks"
    from_port       = 6379
    to_port         = 6379
    protocol        = "tcp"
    security_groups = [aws_security_group.ecs_tasks.id]
  }
  
  tags = {
    Name = "healthcare-elasticache-sg-${var.environment}"
  }
}

Data Storage: RDS PostgreSQL with Encryption

RDS Configuration for HIPAA Compliance

# terraform/rds.tf

# KMS Key for RDS Encryption
resource "aws_kms_key" "rds" {
  description             = "KMS key for RDS encryption - ${var.environment}"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  
  tags = {
    Name      = "healthcare-rds-kms-${var.environment}"
    HIPAA     = "true"
    DataClass = "PHI"
  }
}

resource "aws_kms_alias" "rds" {
  name          = "alias/healthcare-rds-${var.environment}"
  target_key_id = aws_kms_key.rds.key_id
}

# DB Subnet Group (must span multiple AZs)
resource "aws_db_subnet_group" "healthcare" {
  name       = "healthcare-db-subnet-group-${var.environment}"
  subnet_ids = aws_subnet.private_db[*].id
  
  tags = {
    Name  = "healthcare-db-subnet-group-${var.environment}"
    HIPAA = "true"
  }
}

# DB Parameter Group (enforce SSL connections)
resource "aws_db_parameter_group" "healthcare" {
  name   = "healthcare-pg14-${var.environment}"
  family = "postgres14"
  
  # Require SSL for all connections (HIPAA transmission security)
  parameter {
    name  = "rds.force_ssl"
    value = "1"
  }
  
  # Log all DDL statements (audit trail)
  parameter {
    name  = "log_statement"
    value = "ddl"
  }
  
  # Log all connections/disconnections
  parameter {
    name  = "log_connections"
    value = "1"
  }
  
  parameter {
    name  = "log_disconnections"
    value = "1"
  }
  
  tags = {
    HIPAA = "true"
  }
}

# RDS PostgreSQL Instance (Multi-AZ for HA)
resource "aws_db_instance" "healthcare" {
  identifier     = "healthcare-db-${var.environment}"
  engine         = "postgres"
  engine_version = "14.9"
  instance_class = "db.r6g.xlarge"  # Graviton2 for better price/performance
  
  # Storage
  allocated_storage     = 100
  max_allocated_storage = 1000  # Auto-scaling enabled
  storage_type          = "gp3"
  storage_encrypted     = true
  kms_key_id            = aws_kms_key.rds.arn
  
  # Database
  db_name  = "healthcare"
  username = "postgres"
  password = random_password.db_master_password.result
  port     = 5432
  
  # High Availability
  multi_az               = true  # CRITICAL for production
  db_subnet_group_name   = aws_db_subnet_group.healthcare.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  publicly_accessible    = false
  
  # Backups (HIPAA requires 7-year retention)
  backup_retention_period = 35  # 35 days automated backups
  backup_window           = "03:00-04:00"  # UTC
  maintenance_window      = "Mon:04:00-Mon:05:00"
  
  # Additional backups via AWS Backup for 7-year retention
  copy_tags_to_snapshot = true
  skip_final_snapshot   = false
  final_snapshot_identifier = "healthcare-db-final-${var.environment}-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
  
  # Monitoring
  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
  monitoring_interval             = 60
  monitoring_role_arn             = aws_iam_role.rds_monitoring.arn
  performance_insights_enabled    = true
  performance_insights_kms_key_id = aws_kms_key.rds.arn
  performance_insights_retention_period = 731  # 2 years
  
  # Security
  deletion_protection      = true  # Prevent accidental deletion
  parameter_group_name     = aws_db_parameter_group.healthcare.name
  iam_database_authentication_enabled = true
  
  tags = {
    Name      = "healthcare-db-${var.environment}"
    HIPAA     = "true"
    DataClass = "PHI"
  }
  
  lifecycle {
    prevent_destroy = true  # Extra safety for production
  }
}

# Store master password in Secrets Manager
resource "random_password" "db_master_password" {
  length  = 32
  special = true
}

resource "aws_secretsmanager_secret" "db_master_password" {
  name                    = "healthcare/db/master-password-${var.environment}"
  recovery_window_in_days = 30
  kms_key_id              = aws_kms_key.secrets.arn
  
  tags = {
    HIPAA = "true"
  }
}

resource "aws_secretsmanager_secret_version" "db_master_password" {
  secret_id = aws_secretsmanager_secret.db_master_password.id
  secret_string = jsonencode({
    username = aws_db_instance.healthcare.username
    password = random_password.db_master_password.result
    engine   = "postgres"
    host     = aws_db_instance.healthcare.address
    port     = aws_db_instance.healthcare.port
    dbname   = aws_db_instance.healthcare.db_name
  })
}

# Read Replica (for reporting queries, reduces load on primary)
resource "aws_db_instance" "healthcare_read_replica" {
  identifier             = "healthcare-db-replica-${var.environment}"
  replicate_source_db    = aws_db_instance.healthcare.identifier
  instance_class         = "db.r6g.large"
  publicly_accessible    = false
  skip_final_snapshot    = true
  vpc_security_group_ids = [aws_security_group.rds.id]
  
  # Inherit encryption from source
  storage_encrypted = true
  
  tags = {
    Name      = "healthcare-db-replica-${var.environment}"
    HIPAA     = "true"
    DataClass = "PHI"
    Purpose   = "ReadReplica"
  }
}

# AWS Backup Plan (7-year retention for HIPAA)
resource "aws_backup_plan" "healthcare_db" {
  name = "healthcare-db-backup-plan-${var.environment}"
  
  # Daily backups retained for 7 years
  rule {
    rule_name         = "daily-7year-retention"
    target_vault_name = aws_backup_vault.healthcare.name
    schedule          = "cron(0 5 * * ? *)"  # 5 AM UTC daily
    
    lifecycle {
      cold_storage_after = 90   # Move to Glacier after 90 days
      delete_after       = 2555 # 7 years in days
    }
    
    copy_action {
      destination_vault_arn = aws_backup_vault.healthcare_dr.arn
      
      lifecycle {
        cold_storage_after = 90
        delete_after       = 2555
      }
    }
  }
}

resource "aws_backup_selection" "healthcare_db" {
  name         = "healthcare-db-backup-selection-${var.environment}"
  plan_id      = aws_backup_plan.healthcare_db.id
  iam_role_arn = aws_iam_role.backup.arn
  
  resources = [
    aws_db_instance.healthcare.arn
  ]
}

resource "aws_backup_vault" "healthcare" {
  name        = "healthcare-backup-vault-${var.environment}"
  kms_key_arn = aws_kms_key.backup.arn
  
  tags = {
    HIPAA = "true"
  }
}

# DR vault in separate region
resource "aws_backup_vault" "healthcare_dr" {
  provider    = aws.dr_region
  name        = "healthcare-backup-vault-dr-${var.environment}"
  kms_key_arn = aws_kms_key.backup_dr.arn
  
  tags = {
    HIPAA   = "true"
    Purpose = "DisasterRecovery"
  }
}

Application Layer: ECS Fargate

ECS Configuration

# terraform/ecs.tf

# ECS Cluster
resource "aws_ecs_cluster" "healthcare" {
  name = "healthcare-cluster-${var.environment}"
  
  setting {
    name  = "containerInsights"
    value = "enabled"  # Enhanced monitoring
  }
  
  configuration {
    execute_command_configuration {
      kms_key_id = aws_kms_key.ecs.arn
      logging    = "OVERRIDE"
      
      log_configuration {
        cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_exec.name
      }
    }
  }
  
  tags = {
    Name  = "healthcare-cluster-${var.environment}"
    HIPAA = "true"
  }
}

# ECS Task Definition
resource "aws_ecs_task_definition" "healthcare_api" {
  family                   = "healthcare-api-${var.environment}"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "1024"
  memory                   = "2048"
  execution_role_arn       = aws_iam_role.ecs_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn
  
  container_definitions = jsonencode([
    {
      name      = "healthcare-api"
      image     = "${aws_ecr_repository.healthcare_api.repository_url}:${var.app_version}"
      essential = true
      
      portMappings = [
        {
          containerPort = 8080
          protocol      = "tcp"
        }
      ]
      
      environment = [
        {
          name  = "ENVIRONMENT"
          value = var.environment
        },
        {
          name  = "AWS_REGION"
          value = var.aws_region
        }
      ]
      
      secrets = [
        {
          name      = "DATABASE_URL"
          valueFrom = "${aws_secretsmanager_secret.db_master_password.arn}:engine::"
        },
        {
          name      = "REDIS_URL"
          valueFrom = aws_secretsmanager_secret.redis_connection.arn
        }
      ]
      
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.ecs_tasks.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "healthcare-api"
        }
      }
      
      healthCheck = {
        command     = ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
        interval    = 30
        timeout     = 5
        retries     = 3
        startPeriod = 60
      }
    }
  ])
  
  tags = {
    Name  = "healthcare-api-task-${var.environment}"
    HIPAA = "true"
  }
}

# ECS Service
resource "aws_ecs_service" "healthcare_api" {
  name            = "healthcare-api-${var.environment}"
  cluster         = aws_ecs_cluster.healthcare.id
  task_definition = aws_ecs_task_definition.healthcare_api.arn
  desired_count   = 2  # Minimum 2 for HA
  launch_type     = "FARGATE"
  
  network_configuration {
    subnets          = aws_subnet.private_app[*].id
    security_groups  = [aws_security_group.ecs_tasks.id]
    assign_public_ip = false
  }
  
  load_balancer {
    target_group_arn = aws_lb_target_group.healthcare_api.arn
    container_name   = "healthcare-api"
    container_port   = 8080
  }
  
  deployment_configuration {
    maximum_percent         = 200
    minimum_healthy_percent = 100
    
    deployment_circuit_breaker {
      enable   = true
      rollback = true  # Auto-rollback on failed deployment
    }
  }
  
  enable_execute_command = true  # For troubleshooting (sessions logged)
  
  tags = {
    Name  = "healthcare-api-service-${var.environment}"
    HIPAA = "true"
  }
  
  depends_on = [aws_lb_listener.healthcare_api_https]
}

Monitoring & Compliance

CloudWatch Dashboards

# terraform/monitoring.tf

resource "aws_cloudwatch_dashboard" "hipaa_compliance" {
  dashboard_name = "HIPAA-Compliance-${var.environment}"
  
  dashboard_body = jsonencode({
    widgets = [
      {
        type = "metric"
        properties = {
          metrics = [
            ["AWS/RDS", "FreeStorageSpace", { stat = "Minimum", label = "RDS Storage" }],
            ["AWS/RDS", "DatabaseConnections", { stat = "Maximum" }],
            ["AWS/ApplicationELB", "TargetResponseTime", { stat = "Average" }],
            ["AWS/ApplicationELB", "HTTPCode_Target_5XX_Count", { stat = "Sum" }]
          ]
          period = 300
          region = var.aws_region
          title  = "Infrastructure Health"
        }
      },
      {
        type = "log"
        properties = {
          query   = "SOURCE '/aws/vpc/healthcare-${var.environment}' | fields @timestamp, @message | filter action = 'REJECT' | stats count() by srcAddr"
          region  = var.aws_region
          title   = "Rejected Network Traffic (Potential Threats)"
        }
      },
      {
        type = "metric"
        properties = {
          metrics = [
            ["CWAgent", "unauthorized_api_calls", { stat = "Sum" }],
            ["GuardDuty", "findings", { stat = "Sum" }]
          ]
          period = 3600
          region = var.aws_region
          title  = "Security Alerts"
        }
      }
    ]
  })
}

Disaster Recovery Strategy

Multi-Region Failover

RTO (Recovery Time Objective): 4 hours RPO (Recovery Point Objective): 1 hour

DR Strategy:

  1. Automated Backups: RDS automated backups + AWS Backup (cross-region replication)
  2. Infrastructure as Code: Entire stack deployable to DR region via Terraform in < 30 minutes
  3. S3 Cross-Region Replication: PHI data replicated to DR region continuously
  4. Route 53 Health Checks: Automatic DNS failover to DR region if primary unhealthy
# terraform/disaster-recovery.tf

# Route 53 Health Check (primary region)
resource "aws_route53_health_check" "primary" {
  fqdn              = "api.healthcare.example.com"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = 3
  request_interval  = 30
  
  tags = {
    Name = "healthcare-primary-health-check"
  }
}

# Route 53 Failover Record (primary)
resource "aws_route53_record" "primary" {
  zone_id        = aws_route53_zone.healthcare.zone_id
  name           = "api.healthcare.example.com"
  type           = "A"
  set_identifier = "primary"
  
  failover_routing_policy {
    type = "PRIMARY"
  }
  
  health_check_id = aws_route53_health_check.primary.id
  
  alias {
    name                   = aws_lb.healthcare.dns_name
    zone_id                = aws_lb.healthcare.zone_id
    evaluate_target_health = true
  }
}

# Route 53 Failover Record (DR region)
resource "aws_route53_record" "dr" {
  zone_id        = aws_route53_zone.healthcare.zone_id
  name           = "api.healthcare.example.com"
  type           = "A"
  set_identifier = "dr"
  
  failover_routing_policy {
    type = "SECONDARY"
  }
  
  alias {
    name                   = aws_lb.healthcare_dr.dns_name
    zone_id                = aws_lb.healthcare_dr.zone_id
    evaluate_target_health = true
  }
}

Compliance Validation

Automated HIPAA Compliance Checks

# scripts/validate-hipaa-compliance.sh

#!/bin/bash
set -e

ENVIRONMENT=$1
REGION=${2:-us-east-1}

echo "🔍 Running HIPAA Compliance Validation for $ENVIRONMENT environment..."

# Check 1: All S3 buckets storing PHI are encrypted
echo "✓ Checking S3 encryption..."
aws s3api list-buckets --query "Buckets[*].Name" --output text | while read bucket; do
  encryption=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>/dev/null || echo "NONE")
  if [[ "$encryption" == "NONE" ]]; then
    echo "❌ FAIL: Bucket $bucket is not encrypted"
    exit 1
  fi
done

# Check 2: RDS instances are encrypted
echo "✓ Checking RDS encryption..."
aws rds describe-db-instances --region "$REGION" --query "DBInstances[*].[DBInstanceIdentifier,StorageEncrypted]" --output text | while read instance encrypted; do
  if [[ "$encrypted" != "True" ]]; then
    echo "❌ FAIL: RDS instance $instance is not encrypted"
    exit 1
  fi
done

# Check 3: CloudTrail is enabled and logging
echo "✓ Checking CloudTrail status..."
trail_status=$(aws cloudtrail get-trail-status --name healthcare-trail-$ENVIRONMENT --query "IsLogging" --output text)
if [[ "$trail_status" != "True" ]]; then
  echo "❌ FAIL: CloudTrail is not logging"
  exit 1
fi

# Check 4: VPC Flow Logs are enabled
echo "✓ Checking VPC Flow Logs..."
vpc_id=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=healthcare-vpc-$ENVIRONMENT" --query "Vpcs[0].VpcId" --output text)
flow_logs=$(aws ec2 describe-flow-logs --filter "Name=resource-id,Values=$vpc_id" --query "FlowLogs" --output text)
if [[ -z "$flow_logs" ]]; then
  echo "❌ FAIL: VPC Flow Logs not enabled for $vpc_id"
  exit 1
fi

# Check 5: MFA enabled for root account
echo "✓ Checking root account MFA..."
mfa_enabled=$(aws iam get-account-summary --query "SummaryMap.AccountMFAEnabled" --output text)
if [[ "$mfa_enabled" != "1" ]]; then
  echo "❌ FAIL: Root account MFA is not enabled"
  exit 1
fi

echo "✅ All HIPAA compliance checks passed!"

Conclusion

Building a HIPAA-compliant AWS architecture requires careful attention to:

Security: Encryption at rest and in transit, network isolation, least privilege access Monitoring: Comprehensive logging (CloudTrail, VPC Flow Logs, application logs) Availability: Multi-AZ deployments, automated failover, disaster recovery Auditability: Immutable logs, 7-year retention, compliance dashboards

This reference architecture provides a solid foundation for healthcare applications on AWS. Key takeaways:

  1. Always sign AWS BAA before storing PHI
  2. Use only HIPAA-eligible services for PHI workloads
  3. Encrypt everything (KMS for at-rest, TLS 1.2+ for in-transit)
  4. Implement defense in depth (multiple security layers)
  5. Automate compliance validation (infrastructure as code + automated testing)
  6. Plan for disaster recovery (cross-region backups, tested failover procedures)

Implementation Roadmap

Phase 1 (Weeks 1-2): Foundation

  • Set up AWS Organization, sign BAA
  • Deploy VPC with public/private subnets
  • Configure CloudTrail, VPC Flow Logs
  • Set up KMS keys for encryption

Phase 2 (Weeks 3-4): Data Layer

  • Deploy RDS PostgreSQL (Multi-AZ, encrypted)
  • Configure automated backups + AWS Backup
  • Set up Secrets Manager for credentials
  • Deploy ElastiCache Redis

Phase 3 (Weeks 5-6): Application Layer

  • Set up ECR for container images
  • Deploy ECS Fargate cluster
  • Configure ALB with TLS termination
  • Deploy application containers

Phase 4 (Weeks 7-8): Security & Monitoring

  • Enable GuardDuty, Security Hub, Config
  • Set up CloudWatch dashboards and alarms
  • Configure SNS alerts for security events
  • Implement automated incident response

Phase 5 (Weeks 9-10): Disaster Recovery

  • Set up cross-region backups
  • Deploy DR infrastructure (separate region)
  • Test failover procedures
  • Document runbooks

Phase 6 (Week 11-12): Compliance Validation

  • Run automated compliance checks
  • Conduct penetration testing
  • Perform HIPAA risk assessment
  • Prepare for audit

Via Lucra: HIPAA-Compliant AWS Architecture Experts

Via Lucra specializes in designing and implementing HIPAA-compliant AWS architectures for healthcare organizations. Our services include:

  • AWS Architecture Design: Custom reference architectures tailored to your workload
  • Terraform Implementation: Production-ready infrastructure as code with security built-in
  • HIPAA Compliance Automation: Automated validation, monitoring, and audit preparation
  • Disaster Recovery Planning: Multi-region DR strategies with tested failover procedures

Contact us to learn how we've helped 6 healthcare organizations achieve HIPAA compliance on AWS while reducing infrastructure costs by 40% through optimized architecture design.


Last updated: February 2026. AWS services and HIPAA requirements evolve. Always verify current BAA coverage and compliance requirements with AWS and your compliance team.

VL

Via Lucra LLC

Secure cloud and DevSecOps consultancy specializing in healthcare operations platforms for Medicaid, HCBS, and human services organizations.

Prêt à moderniser vos opérations ?

Discutons de la façon dont Via Lucra peut vous aider à créer des opérations de soins conformes et prêtes pour l'audit.

Planifier une consultation