Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Next.js on EC2: The Full Setup, and Everything You Now Own

Sean

Platform Writer

Sep 10, 2026
9 min read

Running Next.js on EC2 means running a Node process on a Linux machine and putting a reverse proxy in front of it. That is the whole architecture, and it works well. The setup takes an afternoon; the part worth thinking about beforehand is the list of things that become permanently yours the moment it goes live, because that list is longer than the tutorial suggests.

Next.js on EC2: The Full Setup, and Everything You Now Own

So this is both: the working setup end to end, and an honest account of the ongoing maintenance, so the decision is made with the full picture rather than half of it.

Table of contents

Instance, security group, and the first mistake

Launch a small instance — two vCPUs and 2GB of memory is a sensible starting point, since Next.js builds are memory-hungry and a 1GB instance will run out during next build in a way that looks like a mysterious hang.

The security group is where the first real decision happens. Open port 443 to the world, port 80 to the world so certificate issuance and the redirect work, and port 22 to your address only.

Do not open port 3000. It is tempting during setup, it works, and it leaves your Node process directly on the internet with no proxy in front of it — which means no rate limiting, no request size limits, no TLS, and every attacker scanning for exposed Node applications finds yours. The application should listen on localhost and only the proxy should reach it.

While you are here, attach an Elastic IP. Without one, stopping and starting the instance changes its address and your DNS silently points at nothing.

Node, the build, and standalone output

Install Node via a version manager rather than the distribution package, so the version matches what you build with locally.

Then the piece most tutorials skip. Next.js can produce a standalone build containing only the files needed to run, with dependencies traced and bundled:

// next.config.js
module.exports = {
  output: 'standalone',
}

This matters more on a server than it looks. Instead of shipping the repository plus a full node_modules, you ship a folder with a server.js in it and start that. Deployments are faster, disk use is far lower, and there is no dependency install step on the server to fail halfway.

The two things standalone does not copy, which have to be handled explicitly:

cp -r public .next/standalone/public
cp -r .next/static .next/standalone/.next/static

Missing that second line produces a site that renders with no styling and no JavaScript, which is a very confusing first result.

A further decision: build on the server or build elsewhere and copy the output. Building on the server is simpler and competes for memory with the running application. Building in CI and shipping the standalone folder is more robust and needs somewhere to run the build. For a single small instance, building on the server is fine as long as it has the memory.

Keeping the process alive

A Node process started over SSH dies when the session ends. You need something to supervise it.

A process manager such as pm2 is the common choice and takes about a minute:

pm2 start .next/standalone/server.js --name web -i max
pm2 startup
pm2 save

The -i max runs one instance per CPU core in cluster mode, which matters because Node is single-threaded and a two-core instance running one process uses half the machine. pm2 startup plus pm2 save is what makes it come back after a reboot, and forgetting the pair is why a site vanishes after an unattended instance restart.

A systemd unit does the same job with no extra dependency and is arguably the better answer on a machine you control. Either is fine. Having neither is not.

Set PORT and HOSTNAME=127.0.0.1 in the environment so the server binds to localhost only, which is what makes the closed port 3000 above actually enforceable.

The reverse proxy and the certificate

nginx in front, terminating TLS and forwarding to the Node process:

server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
        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;
        proxy_cache_bypass $http_upgrade;
    }
}

The forwarded-proto header is not optional. Without it Next.js believes requests arrived over plain HTTP, which breaks absolute URL generation and any redirect logic that depends on the scheme.

Then issue a certificate with certbot, which will rewrite the block above to add the TLS listener and the redirect. Confirm the renewal timer is active — systemctl list-timers | grep certbot — because a certificate that silently fails to renew takes the site down completely on a date nobody has in their calendar.

One more nginx setting worth adding: client_max_body_size, which defaults to 1MB. Any upload larger than that fails at the proxy with a 413 before your application sees it.

Deploying a second time

The first deploy is the easy one. The second is where the design shows.

A naive redeploy is: pull, build, restart. During the build the machine is under load, and during the restart the site is down. On a small instance the build alone can make the running site unresponsive.

Better, in increasing order of effort:

  • Build into a new directory and switch a symlink, then reload the process manager. The downtime becomes a process restart rather than a build.
  • Use pm2 cluster mode’s reload, which restarts workers one at a time so there is always one serving.
  • Build in CI and ship the standalone artefact, so the instance never builds at all.

Also plan for rollback. The advantage of the symlink arrangement is that reverting is pointing it at the previous directory and reloading, which takes seconds. Without something like it, rollback means checking out an old commit and rebuilding, on a machine that is currently serving a broken site.

What you now own

The site is live. The recurring list:

  • Operating system security updates, and the reboots some of them need.
  • Node version upgrades, on their own support schedule.
  • Certificate renewal, and noticing if it fails.
  • Disk space. Old builds, pm2 logs and the nginx access log all grow; log rotation needs configuring, and a full disk takes the site down in a way that is initially baffling.
  • Monitoring, because with one instance there is nothing else to notice an outage.
  • Backups, if anything stateful lives on this machine.
  • Capacity. One instance has one ceiling, and adding a second means a load balancer and shared session handling.
  • Image optimisation, which needs sharp installed and enough memory, and which is a common source of production-only errors.

None of it is hard. All of it is real, and it arrives at inconvenient times. For a team that wants a machine they control, that is a fair trade. For a team of three shipping a product, it is worth pricing honestly against the alternative rather than assuming a server is the cheap option because the instance is.

How this fits the rest of the stack

The trade is control against attention, and it is a genuine trade rather than a rhetorical one — the setup above is solid and plenty of teams run it happily for years. The part worth doing before committing is pricing the whole shape rather than the instance: the application, the database, the storage and the bandwidth, which is what the RunxBuild hosting calculator lists as separate lines.

RunxBuild deploys Next.js from a GitHub repository with the build, the process supervision, the proxy and the certificate handled, plus build logs, a live route, environment variables, custom domains, runtime logs, metrics and rollback to a previous deploy — which covers most of the ownership list above. Managed Postgres or MySQL sits beside it on a private network, and autoscaling runs between a floor and a ceiling plan you choose.

Useful related references:

FAQ

How do I deploy Next.js on EC2?

Launch an instance with at least 2GB of memory, install Node, build with standalone output, run the server under a process manager such as pm2 or systemd bound to localhost, and put nginx in front to terminate TLS and proxy to it. Then issue a certificate and confirm the renewal timer is active.

Should I use pm2 or systemd for Next.js?

Either works. pm2 is quicker to set up and gives cluster mode for free, which uses every CPU core rather than one. systemd adds no dependency and is arguably better on a machine you fully control. What matters is that something restarts the process on failure and after a reboot.

What is standalone output in Next.js?

A build mode that produces a self-contained folder with a server.js and only the traced dependencies, so you do not ship node_modules or install on the server. Remember to copy the public folder and the static folder into it manually, or the site renders without styles or JavaScript.

Do I need nginx in front of Next.js?

You need something terminating TLS and shielding the Node process. nginx is the common choice and adds request limits, header control and static file handling. Exposing the Node port directly to the internet means no TLS, no rate limiting and no request size limits.

Why does my Next.js build fail on a small EC2 instance?

Usually memory. Building is far more demanding than serving, and a 1GB instance frequently runs out during the build, appearing as a hang or a killed process. Either use a larger instance, add swap, or build elsewhere and ship the standalone output.

#ec2 nextjs#deploy nextjs#nextjs on aws#pm2#nginx reverse proxy