AWS

How to Build and Deploy a Java Application on AWS Using ECS and Fargate – Part 4

Over the past few parts (Part 1, Part 2, Part 3), we’ve learned how to containerize the app, deploy it to ECS Fargate, attach an ALB, and even manage DNS via Route 53 with our Books API setup example. The Books API is running publicly on an Application Load Balancer (ALB). But right now it’s served over HTTP, which isn’t secure. As part of this blog we will set it up with HTTPS and make sure we control who can hit the endpoint while we’re testing.

Why HTTPS?

HTTP is unencrypted: anyone monitoring the network could see your data.

HTTPS = HTTP + TLS (Transport Layer Security) ensures:

  • Encryption – nobody can read your requests/responses
  • Authentication – clients know they’re talking to your domain
  • Integrity – traffic cannot be tampered with

With AWS, we use ACM (AWS Certificate Manager) to issue a certificate, and the ALB terminates HTTPS for us. This means the containers inside ECS can continue serving plain HTTP inside the VPC.

Step 1: Add an ACM Certificate

We want a public certificate for our subdomain: api.aparnavikraman.dev. Using CDK:

import * as cdk from "aws-cdk-lib";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as route53 from "aws-cdk-lib/aws-route53";

export class CertificateStack extends cdk.Stack {
  public readonly certificate: acm.ICertificate;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", {
      domainName: "aparnavikraman.dev",
    });

    this.certificate = new acm.DnsValidatedCertificate(this, "ApiCert", {
      domainName: "api.aparnavikraman.dev",
      hostedZone,
      region: this.region,
    });
  }
}

This automatically creates a DNS validation record in Route 53 and issues the certificate.

Step 2: Update ALB Listener for HTTPS

Update the listenerPort to 443 and protocol to HTTPS and add the certification created in Step 1. I have also enabled redirect from http to https.

// Fargate Service with ALB
    this.service = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BooksService', {
      cluster: cluster,
      cpu: 256,
      memoryLimitMiB: 512,
      desiredCount: 1,
      protocol: elbv2.ApplicationProtocol.HTTPS,
      listenerPort: 443,
      certificate: props.certificate,
      redirectHTTP: true,
      taskImageOptions: {
        image: ecs.ContainerImage.fromAsset('/Users/himanshusourav/book-api/books-api', {
            platform: ecr_assets.Platform.LINUX_AMD64
        }), // Path to your Dockerfile
        containerPort: 8080,
      },
      publicLoadBalancer: true,
    });

Step 3: Protect Access While Testing

To avoid random users hitting your API, restrict the ALB to your IP:

import * as ec2 from "aws-cdk-lib/aws-ec2";

const myIp = "MY_PUBLIC_IP/32"; // Use command curl <https://checkip.amazonaws.com>
loadBalancer.connections.allowFrom(ec2.Peer.ipv4(myIp), ec2.Port.tcp(443));
  • Only your IP can reach the API over HTTPS.
  • You can later remove this restriction for production.

Step 4: Test the Endpoint

Once deployed,

curl -v <https://api.aparnavikraman.dev/books>

Expected output:

["Harry Potter", "The Fourth Wing", "The Midnight Library", "The Way of Kings"]

If it doesn’t work:

  • Make sure ACM certificate is Issued
  • Check your security group allows inbound 443 for your IP
  • DNS must be propagated (api.aparnavikraman.dev → ALB)

The TLS handshake can be verified by running the command

curl -v <https://api.aparnavikraman.dev/books>

and output as below

*   Trying 54.214.121.153:443...
* Connected to api.aparnavikraman.dev (54.214.121.153) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=api.aparnavikraman.dev
*  start date: Oct  5 00:00:00 2025 GMT
*  expire date: Nov  3 23:59:59 2026 GMT
*  subjectAltName: host "api.aparnavikraman.dev" matched cert's "api.aparnavikraman.dev"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for <https://api.aparnavikraman.dev/books>
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: api.aparnavikraman.dev]
* [HTTP/2] [1] [:path: /books]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
> GET /books HTTP/2
> Host: api.aparnavikraman.dev
> User-Agent: curl/8.4.0
> Accept: */*
> 
< HTTP/2 200 
< date: Sun, 05 Oct 2025 04:22:43 GMT
< content-type: application/json
< 
* Connection #0 to host api.aparnavikraman.dev left intact
["Harry Potter", "The Fourth Wing", "The Midnight Library", "The Way of Kings"]% 

Debugging Notes:

While running cdk deploy --all

ValidationError: The HTTPS protocol must be used when redirecting HTTP traffic

I missed to update the protocol protocol: elbv2.ApplicationProtocol.HTTPS, as part of the service creation step and it was defaulting to http.


In Part 5, we will

  • Set up auto-scaling for your ECS tasks
  • Add CloudWatch monitoring and alarms

Series Roadmap

  1. (Done) Build and containerize the Java app
  2. (Done) Deploy to ECS Fargate with ALB (via CDK)
  3. (Done) Add Route 53 and a custom domain
  4. (This Post) Secure with HTTPS (ACM) and protect the endpoint
  5. Scaling and monitoring

Leave a Reply

Your email address will not be published. Required fields are marked *