If you’ve ever had a database crash at 3am and brought down your entire app, you already understand why Multi-AZ exists. I wish someone had pushed me toward it earlier in my career. Instead I learned the hard way, with an angry Slack channel and a single-AZ MySQL instance that decided to take a nap during a hardware failure.

This project is everything I’d do differently now. A Node.js app on EC2, a MySQL database on RDS sitting in a private subnet with Multi-AZ failover, a real domain through Route 53, and an SSL cert from Let’s Encrypt. Nothing fancy, but it’s the kind of setup that actually survives the small disasters that happen in production.

You can watch the full video walkthrough here:

What We’re Actually Building

Three AWS services do most of the work: Route 53 for DNS, EC2 for the app, and RDS for the database. The interesting part isn’t any one of them — it’s how they fit together inside a VPC.

The EC2 instance lives in a public subnet because it needs to talk to the internet. The RDS instance lives in a private subnet because it absolutely should not. That single decision — putting the database somewhere the internet can’t reach it — is one of those small things that separates a hobby project from something you’d let a real user touch.

The Multi-AZ part is what makes this actually production-ready. RDS keeps a standby copy of your database in a second availability zone, and if the primary instance dies, AWS flips over to the standby automatically. You don’t have to do anything. Your DNS endpoint stays the same and the application just keeps working after a brief pause.

Security Groups Are Where Most People Mess This Up

I’ll save you some pain here. Security groups are the thing I see beginners get wrong over and over again. They either open everything to the world, or they lock things down so tight that nothing can talk to anything.

For this setup you want two security groups. One for EC2 that allows inbound HTTP, HTTPS, and SSH from the internet. Pretty standard stuff. The second one is for RDS, and here’s the part people miss — instead of allowing port 3306 from an IP range, you allow it from the EC2 security group itself.

This is a small detail that matters a lot. It means only instances inside that EC2 security group can ever reach the database, no matter what IPs change or what new instances come online. You’re trusting a group, not a network. Get used to writing rules this way. It’s cleaner and you’ll thank yourself later.

Spinning Up the EC2 Instance

Nothing dramatic here. Launch an Ubuntu instance, attach the EC2 security group you just made, grab the key pair, and SSH in. I usually go with a t3.small or t3.medium for tutorial-style projects because the t2 burst credits run out faster than you’d expect when you’re installing Docker and Node and a bunch of other things at the same time.

Once the instance is up, head over to Route 53 and create an A record that points your domain or subdomain to the instance’s public IP. If you don’t own a domain yet, you can buy one through Route 53 itself for around twelve bucks a year. It’s not the cheapest registrar out there but having everything in one place is genuinely worth it.

Clone the Node.js repo and we’ll come back to it after RDS is ready.

Setting Up RDS the Right Way

This is the part everyone wants to rush through, and this is also where most of the value of the whole setup lives.

When you create the RDS instance, pick MySQL, then look for the deployment options. You’ll see three choices: single-AZ, Multi-AZ DB instance, and Multi-AZ DB cluster. Pick Multi-AZ DB Instance. The single-AZ option is fine for development but not for anything you care about. The cluster option is more expensive and more complex than what most projects actually need.

Before you finish, you’ll need to set up a subnet group. This is basically you telling AWS which subnets RDS is allowed to use for the primary and standby instances. Make sure they’re in different availability zones — that’s literally the whole point. If you put both in the same AZ, you’ve just paid for Multi-AZ without getting any of the benefits.

Set the database to not be publicly accessible. Attach the RDS security group from earlier. Pick a master username and password and write them down somewhere safe because you absolutely will need them in five minutes and absolutely will have forgotten them.

The instance takes a while to come up. Multi-AZ instances especially. Grab coffee.

Connecting the App

Once RDS is ready, copy the endpoint from the console. It’ll look something like your-db-name.cluster-xxxxx.us-east-1.rds.amazonaws.com. That’s what your Node.js app will use as the database host. Not an IP — the endpoint. This is important because when failover happens, the IP behind that endpoint changes but the endpoint itself stays the same. Your app doesn’t need to know about the switch.

Plug the endpoint, username, password, and database name into your app’s environment variables or config file. Run your migrations to create the schema. Insert a test record. Refresh the page. If you see the data, you’re done with the hard part.

The Deployment Script Trick

The video uses a deployment script that handles Docker, Nginx, and Let’s Encrypt all at once. Honestly, if you’ve ever set these up manually you know how much of a slog it can be. Certbot in particular has bitten me more times than I’d like to admit, usually because of some Nginx config that doesn’t quite match what Certbot expects.

Having one script that does all of it means you can blow away the EC2 instance, spin up a new one, run the script, and be back online in maybe ten minutes. That’s the kind of repeatability you want. Manual server setup is fine the first time. Doing it the third time at midnight because something broke is when you wish you’d written a script.

Testing the Failover

This is the fun part and also the part that always makes me a little nervous, even though I know it’s supposed to work.

In the RDS console, hit Actions, then Reboot, and check the “Reboot with failover” option. What this does is force AWS to switch the primary and standby roles. The DNS endpoint stays the same but the underlying IP changes. Your app should briefly lose its database connection and then reconnect to the new primary, usually within a minute or two.

If your app is properly handling connection drops, this is barely noticeable. If it isn’t, you’ll see errors for a moment and your users might too. Either way, watching it work the first time is genuinely satisfying. You’re seeing exactly what would happen during a real hardware failure, except you’re causing it on purpose.

A Word on Monitoring

The RDS console has performance insights and log events built in. I’d recommend poking around there even if you don’t think you’ll need them yet. CloudWatch alarms on CPU, free memory, and database connections are basic stuff that take five minutes to set up and will save you eventually. You won’t appreciate them until your app starts behaving weirdly and you have no idea why, and then you’ll be very glad past you set them up.

Don’t Forget to Tear It Down

This is the boring footer to every AWS tutorial but I’m going to say it anyway because I have personally forgotten and personally paid for it. Multi-AZ RDS instances are not cheap. A t3.medium running 24/7 in two availability zones with a real domain and an Elastic IP will run you something like forty to sixty dollars a month if you leave it on.

When you’re done with the project, delete the RDS instance (skip the final snapshot if you don’t need it), terminate the EC2 instance, release the Elastic IP if you grabbed one, and remove the Route 53 hosted zone if you’re not using it for anything else. The domain itself you can keep — that’s a flat annual cost — but the hourly stuff adds up faster than you’d think.

Final Thoughts

The thing I want you to take away from this isn’t the specific clicks. It’s the pattern. Public subnet for the app, private subnet for the database, security groups that reference each other instead of IP ranges, Multi-AZ for anything that matters, and a deployment script so you don’t have to remember how you did it last time.

Once you’ve done this once, you can do it again for any web app. Different framework, different language, different database — the architecture stays the same. That’s why it’s worth taking the time to actually understand it instead of just following a checklist.

If you ran into something weird while following along, drop a comment on the video. Half the time someone else has hit the same thing and figured it out, and the other half it’s a chance for me to update the explanation. Either way, good luck with the build.

Madhukar Reddy

DevOps engineer focused on AWS, Docker, Kubernetes, cloud infrastructure, and cyber security. Shares practical cloud and DevOps content based on hands-on deployments, infrastructure troubleshooting, and real-world projects.

$ This blog is currently running on AWS EC2 using Docker-based deployment.

Leave a response