MK
Manjit KumarFull Stack Developer
Contact
DevOps

Deploying a Node.js Backend on AWS EC2 with Nginx

17 October 20269 min read4 views
Deploying a Node.js Backend on AWS EC2 with Nginx
#aws#ec2#node#nginx#devops

Deploying a Node.js Backend on AWS EC2 with Nginx

Writing a backend API is only one part of building a software application. To make it accessible to other users, you need to deploy it to a server and configure the infrastructure around it.

For Node.js developers, AWS EC2 provides virtual servers for running applications, while Nginx can act as a reverse proxy between incoming requests and your backend.

In this guide, we'll walk through the essential steps involved in deploying a Node.js backend on an EC2 instance.

1. Understanding the Architecture

Before touching the terminal, let's understand how the components fit together.

A typical deployment looks like this:

text
User | v Internet Request | v AWS EC2 Instance | v Nginx | v Node.js Backend API | v Database

Each component has a specific responsibility:

  • AWS EC2: Provides the virtual server where your application runs.
  • Nginx: Receives HTTP requests and forwards them to the backend.
  • Node.js: Executes your application code and handles API requests.
  • Database: Stores application data, usually through a managed service or a separately configured database server.

The database is optional for this particular deployment workflow, but most real-world APIs require some form of persistent storage.

2. Launch an EC2 Instance

Start by opening the AWS Management Console and navigating to EC2.

Launch an instance with a supported Linux distribution, such as Ubuntu Server.

For a learning project, a small instance may be sufficient. Choose an instance type based on your application's requirements and AWS pricing.

During setup, configure the following:

  • Select an appropriate Amazon Machine Image (AMI).
  • Choose an instance type.
  • Create or select an SSH key pair.
  • Configure the network and security group.
  • Allocate storage appropriate for your application.

Configure the Security Group

A security group controls inbound and outbound traffic for your instance.

For a basic web deployment, you may need these inbound rules:

PortProtocolPurpose
22TCPSSH administration
80TCPHTTP traffic
443TCPHTTPS traffic

Restrict SSH access to your own IP address whenever possible. Avoid opening port 22 to the entire internet.

You generally don't need to expose your Node.js application's internal port publicly if Nginx will receive the external requests.

3. Connect to the Server

After the instance starts, copy its public IP address and connect using SSH.

On a Linux or macOS terminal:

bash
chmod 400 your-key.pem ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP

Replace the key filename and public IP with your actual values.

The default username depends on the AMI. For Ubuntu images, it is commonly ubuntu.

Once connected, update the package index:

bash
sudo apt update

Install basic utilities if needed:

bash
sudo apt install -y git curl

You now have a remote Linux environment in which to prepare your application.

4. Install Node.js

Your server needs a compatible Node.js runtime to execute the backend.

Choose a supported Node.js LTS version that matches your application's requirements. Install it using a trusted installation method, such as the official Node.js distribution instructions or a maintained version manager.

Verify the installation:

bash
node --version npm --version

The exact versions should be compatible with your project's dependencies.

Tip: Avoid installing an arbitrary runtime version just because it is available in your operating system's default package repository.

5. Deploy Your Backend Code

Clone your repository onto the server:

bash
git clone YOUR_REPOSITORY_URL

Move into the project directory:

bash
cd YOUR_PROJECT_DIRECTORY

Install the project dependencies:

bash
npm ci

This command expects a compatible package-lock.json.

Before continuing, make sure the application starts correctly on the server.

For example, if your project defines a start script:

bash
npm start

Your backend should listen on an appropriate port, such as 3000.

A typical Node.js server might use:

javascript
const express = require("express"); const app = express(); const PORT = process.env.PORT || 3000; app.get("/health", (req, res) => { res.json({ status: "ok" }); }); app.listen(PORT, "127.0.0.1", () => { console.log(`Server listening on port ${PORT}`); });

This example binds to the loopback interface because Nginx will run on the same machine.

If your existing backend already defines its routes and startup logic, use that implementation instead of creating a second server.

Test the endpoint locally on the EC2 instance:

bash
curl http://127.0.0.1:3000/health

You should receive a successful response from your application.

6. Keep the Backend Running with PM2

Running npm start manually is useful for testing, but it isn't a reliable way to manage a long-running application.

If your SSH session closes or the process exits, you need a proper process-management strategy.

PM2 is one option for managing Node.js applications.

Install it:

bash
sudo npm install -g pm2

Start the application using its npm script:

bash
pm2 start npm --name my-api -- start

Check the process status:

bash
pm2 status

Inspect the logs:

bash
pm2 logs my-api

Save the current process list:

bash
pm2 save

To configure startup after a server reboot, run:

bash
pm2 startup

Follow the command PM2 prints, then run pm2 save again if needed.

The generated startup command depends on the server environment, so use the exact instructions shown by PM2.

7. Install and Configure Nginx

Nginx will receive incoming HTTP requests and forward them to the Node.js application.

Install it:

bash
sudo apt install -y nginx

Check that the service is running:

bash
sudo systemctl status nginx

Create a site configuration:

bash
sudo nano /etc/nginx/sites-available/my-api

Add the following configuration:

nginx
server { listen 80; server_name YOUR_DOMAIN_OR_PUBLIC_IP; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

Replace YOUR_DOMAIN_OR_PUBLIC_IP with your domain or the appropriate server address.

Enable the configuration:

bash
sudo ln -s /etc/nginx/sites-available/my-api /etc/nginx/sites-enabled/my-api

If the default Nginx site conflicts with your configuration, disable it after confirming it is no longer needed.

Validate the configuration:

bash
sudo nginx -t

If the test succeeds, reload Nginx:

bash
sudo systemctl reload nginx

Nginx can now forward HTTP requests to your backend.

8. Understand What a Reverse Proxy Does

A reverse proxy accepts client requests and forwards them to an application server.

Without Nginx, a user might need to access your application through a URL containing its internal port.

With Nginx, users can access the application through the standard HTTP or HTTPS endpoint while Nginx forwards requests internally.

A reverse proxy can also help with:

  • TLS termination
  • Request routing
  • Access logging
  • Serving static files
  • Handling multiple backend services

Nginx does not automatically make an application highly available. That requires additional architecture and operational work.

9. Connect Your Domain and Enable HTTPS

If you own a domain, create a DNS record that points to your EC2 instance's public IP address.

For a stable production endpoint, consider using an Elastic IP or another appropriate DNS target so that replacing or restarting infrastructure doesn't unexpectedly change the address users rely on.

Before enabling HTTPS, confirm that:

  • The domain resolves correctly.
  • Port 80 is reachable.
  • Nginx is serving the expected site.
  • Your security group permits the required traffic.

You can then configure TLS using a trusted certificate authority and a suitable certificate-management method, such as Certbot with a compatible Nginx setup.

After HTTPS is configured, verify that the certificate renews automatically and redirect HTTP requests to HTTPS where appropriate.

Never send production credentials or sensitive application data over plain HTTP.

10. Configure Environment Variables Securely

Applications often need environment-specific configuration, such as:

  • Database connection strings
  • Authentication secrets
  • Third-party API keys
  • Application environment settings

Do not hardcode secrets into your source code or commit them to Git.

For a small deployment, you may load a protected environment file through your process manager or application startup configuration.

For production environments, consider a dedicated secrets-management service and restrict access to sensitive values.

Also make sure your application handles missing configuration safely and does not expose secrets through logs or error responses.

11. Test the Deployment

Once the backend and Nginx are configured, test the complete request path.

Check the application process:

bash
pm2 status

Check Nginx:

bash
sudo nginx -t sudo systemctl status nginx

Test the backend locally:

bash
curl http://127.0.0.1:3000/health

Test the public endpoint:

bash
curl http://YOUR_DOMAIN_OR_PUBLIC_IP/health

If you enabled HTTPS, test the HTTPS URL as well.

If something fails, inspect the relevant logs:

bash
pm2 logs my-api
bash
sudo tail -f /var/log/nginx/error.log

These checks help distinguish an application problem from a reverse-proxy, network, or process-management issue.

12. Common Deployment Mistakes

Exposing every application port

Only expose the ports required for public access. Keep internal services private where possible.

Running the backend only in an SSH session

Use a process manager or a suitable service manager to keep the application running reliably.

Forgetting HTTPS

A publicly accessible API should use HTTPS when transmitting sensitive information or authenticating users.

Hardcoding secrets

Keep credentials out of source control and container images. Restrict access to production configuration.

Ignoring logs and monitoring

A successful deployment is not the end of the process. You also need to detect failures, monitor resource usage, and understand application errors.

Assuming one EC2 instance guarantees high availability

A single server remains a potential single point of failure. More resilient deployments require additional infrastructure, health checks, backups, and recovery planning.

Final Thoughts

Deploying a Node.js backend on AWS EC2 is a useful way to understand the connection between application code and infrastructure.

You learn how to provision a Linux server, install a runtime, run a backend process, configure a reverse proxy, manage network access, and prepare a domain for secure access.

These fundamentals are valuable even when you later move to Docker, managed container services, or Kubernetes.

A successful deployment isn't just an API that responds to requests. It's an application that can be configured, monitored, secured, and restarted reliably.

Share

© 2026 Manjit Kumar - All rights reserved

Last updated

With Love, By Manjit.

;