How to Deploy Next.js to Hostinger: A Step-by-Step Guide

By LearnWebCraft Team13 min readIntermediate
next.jsdeploymenthostingervpsnginxpm2

So, you did it. You wrestled with components, tamed state management, and finally, your Next.js application is a gleaming masterpiece. It runs beautifully on localhost:3000. But now... now comes the real boss battle: deployment. That moment when "it works on my machine" has to become "it works for the world."

If you've ever felt that slight sense of panic, you are definitely not alone. Taking a local project and putting it out on a live server can feel a bit like venturing into a dark, scary forest. But I'll let you in on a secret—it’s not magic. It’s just a series of logical steps. Today, we're going to walk through that forest together, and we're going to deploy a Next.js app to a Hostinger VPS.

So why a VPS? Honestly, it's all about control. It's your own little slice of the internet. And why Hostinger? Because I've found it offers a great balance of performance and price, making it perfect for projects big and small. By the time we're done here, you'll have a production-ready, secure, and auto-restarting Next.js app live for everyone to see.

Alright, let's get our hands dirty.

What You'll Need to Get Started

Before we jump in, let's just do a quick check to make sure you have these things ready:

  • A Hostinger VPS plan. The entry-level ones are more than enough to get started.
  • Your Next.js app pushed to a Git repository (like GitHub).
  • A domain name pointing to your server's IP address (this is optional, but I highly recommend it).
  • A terminal or SSH client. If you're on macOS or Linux, you're all set. On Windows, you can use PowerShell or WSL.

Got it all? Awesome. Let's do this.

Step 1: Meet Your Server (SSH & Initial Setup)

First things first, we need to actually connect to our brand-new, empty server. We'll do this using SSH (Secure Shell), which is basically a secure tunnel into your VPS's command line. You'll find your server's IP address and root password right in your Hostinger dashboard.

Open your terminal and type this in:

ssh root@your_server_ip

It'll ask for your password. Go ahead and type it in (you won't see the characters, which is a normal security thing!), press Enter, and... boom—you're in. Kinda feels powerful, right?

Now, the very first thing any good developer does on a fresh server is update everything. Think of it like washing your hands before you start cooking. It's just good hygiene.

apt update && apt upgrade -y

This command grabs the latest list of available software from the internet and then upgrades everything on the system. The -y flag just tells it to automatically say "yes" to any prompts that pop up.

Step 2: Setting Up the Foundation - Node.js

Our Next.js app needs Node.js to run; it's the engine that makes the whole thing go. We can't just install any old version, though. We want a stable, long-term support (LTS) version. Now, I'm personally a big fan of using nvm (Node Version Manager) for flexibility, but for a dedicated server like this, I think installing it directly is a bit cleaner.

We'll use NodeSource's repository to grab a recent LTS version, like Node.js 20.

# Get the setup script for Node.js 20.x
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -

# Now, install Node.js itself
sudo apt install -y nodejs

Let's just quickly double-check that it worked.

node -v  # Should show something like v20.x.x
npm -v   # And a corresponding npm version

Perfect. Our server now officially speaks JavaScript. And hey, while we're at it, I highly recommend installing pnpm. It's a faster, more disk-space-efficient package manager than npm. Trust me on this one, you'll thank me later.

npm install -g pnpm

Step 3: Getting Your Code onto the Server

Okay, the server is prepped and ready. Time to bring in our masterpiece. We'll clone it directly from your Git repository. I like to keep my projects organized in the /var/www directory—it's a pretty standard convention.

# Navigate to the directory where web projects live
cd /var/www

# Clone your project
git clone https://github.com/your-username/your-awesome-app.git

# Move into your project's new home
cd your-awesome-app

Now, just like you would on your local machine, we need to install all the dependencies.

# If you're using pnpm (you should be!)
pnpm install

# Or if you're sticking with npm
npm install

Next up is a really crucial step: environment variables. You should never commit secrets like API keys or database URLs to your Git repository. Instead, we'll create a special .env.production.local file directly on the server.

nano .env.production.local

This command opens a simple little text editor right in your terminal. Go ahead and add all your production secrets here.

# .env.production.local

DATABASE_URL="your-production-database-url"
NEXTAUTH_SECRET="generate-a-super-long-random-string-for-this"
NEXT_PUBLIC_API_URL="https://yourdomain.com/api"

To save and exit in nano, just press Ctrl+X, then Y to confirm, and then Enter.

With our dependencies installed and our secrets safely in place, let's build the app for production.

pnpm build

This command takes all your developer-friendly code and transforms it into a highly optimized bundle of static files and server-side code that's ready to fly.

Step 4: Keeping Your App Alive with PM2

So, we can just start our app with pnpm start and call it a day, right? Well, yes, but what happens if it crashes? Or what if the server reboots in the middle of the night? You’d have to SSH back in and start it manually. That’s… not exactly production-ready.

Enter PM2. Think of PM2 as a dedicated, tireless babysitter for your app. It will watch over it, restart it if it crashes, and even help it run across multiple CPU cores. For any serious Node.js deployment, it's pretty much a non-negotiable.

Let's install it globally on the server.

npm install -g pm2

Now, instead of just running pnpm start ourselves, we'll tell PM2 to handle it.

pm2 start pnpm --name "next-app" -- start

Let's quickly break down that command. We're telling PM2 to:

  • start a new process using the pnpm command.
  • Give it a friendly name, in this case, "next-app".
  • The actual script that pnpm should run is start.

You can check on your app's status anytime with pm2 list. If you want to see its logs in real-time (which is super helpful for debugging), just use pm2 logs next-app.

The final piece of PM2 magic is making sure it starts up automatically when the server does.

pm2 startup

PM2 will spit out a command for you to run. Just copy that line, paste it back into the terminal, and run it. This registers PM2 as a system service. Now, we just have to save the current list of apps we're running so PM2 remembers what to start on a reboot.

pm2 save

And that's it! Your Next.js app is now running and being watched over by its very own guardian angel.

Step 5: The Bouncer - Setting Up Nginx as a Reverse Proxy

Right now, your app is happily running on a port, probably 3000. But users don't type yourdomain.com:3000 into their browser. They just type yourdomain.com. We need something to sit out front, listen for web traffic on the standard ports (80 for HTTP and 443 for HTTPS), and forward it along to our app.

That something is Nginx. It's a super high-performance web server that we'll use as a reverse proxy. I like to think of it as the bouncer at a club: it stands at the front door, checks IDs, and directs people to the right party.

First, let's get it installed.

sudo apt install -y nginx

Now, we need to create a special configuration file for our site. We're basically going to tell Nginx, "Hey, when a request comes in for yourdomain.com, I need you to pass it over to our Next.js app running on localhost:3000."

sudo nano /etc/nginx/sites-available/yourdomain.com

Paste this configuration in. I know it looks a little intimidating, but it's mostly standard boilerplate.

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        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;
    }
}

Save and exit nano. Now, we need to enable this configuration by creating a symbolic link to it in the sites-enabled directory. You can think of this as flipping the "on" switch for our new site.

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/

It's also a good idea to remove the default Nginx welcome page config so it doesn't get in the way.

sudo rm /etc/nginx/sites-enabled/default

Before we restart Nginx, it's always smart to test our configuration file to make sure we didn't make any typos.

sudo nginx -t

If it says the syntax is ok and the test is successful, you're golden. Let's apply our changes.

sudo systemctl restart nginx

Now, for the moment of truth. If you visit your server's IP address or your domain in a browser... you should see your Next.js app! How cool is that?

Step 6: The Padlock - Securing Your Site with SSL

We're live, but we're not quite secure. See that "Not Secure" warning in the browser? Ugh. We need HTTPS. Years ago, this was a surprisingly painful and expensive process. Today, it's free and takes about two minutes, thanks to the amazing folks at Let's Encrypt and a tool called Certbot.

Certbot is a little client that automatically gets SSL certificates from Let's Encrypt and then configures Nginx to use them. It's fantastic.

# Install Certbot and its Nginx plugin
sudo apt install -y certbot python3-certbot-nginx

Now, we just run Certbot. It's smart enough to read your Nginx configuration, see the server_name you set up earlier, and ask you which domain you want to secure.

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will walk you through a few simple questions:

  1. Enter your email for renewal notices.
  2. Agree to their terms of service.
  3. Decide if you want to share your email (that's up to you).
  4. This one's important: It will ask if you want to redirect all HTTP traffic to HTTPS. Choose option 2. This is always the best practice.

And... believe it or not, that's it. Seriously. Certbot will automatically get the certificate, update your Nginx configuration to use it, and even set up a cron job to renew the certificate for you before it expires.

To put the final lock on the door, we'll set up a basic firewall with ufw (which stands for Uncomplicated Firewall, and it really lives up to its name).

# Allow SSH connections (so we don't lock ourselves out!)
sudo ufw allow 'OpenSSH'

# Allow our secured web traffic
sudo ufw allow 'Nginx Full'

# Turn on the firewall
sudo ufw enable

Press y to confirm, and you're all set. Your server is now only accepting traffic on the ports we've explicitly allowed.

Step 7: The "Magic Button" - A Simple Deployment Script

You're not going to want to repeat all those command-line steps every time you push a change, right? Of course not. So let's create a simple bash script to automate our deployment process.

Back in your project directory (/var/www/your-awesome-app), let's create a new file.

nano deploy.sh

Paste this script inside:

#!/bin/bash

# Navigate to the app directory
cd /var/www/your-awesome-app

# Pull the latest changes from the main branch
git pull origin main

# Install dependencies
echo "📦 Installing dependencies..."
pnpm install

# Build the app
echo "🔨 Building for production..."
pnpm build

# Restart the app with PM2
echo "🔄 Restarting the application..."
pm2 restart next-app

echo "✅ Deployment finished successfully!"

Save and exit. The last thing we need to do is make this script executable.

chmod +x deploy.sh

From now on, whenever you push new code to your main branch, you can just SSH into your server, navigate to your project folder, and run:

./deploy.sh

One command to pull, install, build, and restart. That's the developer dream. For a more advanced setup, you could integrate this into a GitHub Action for true CI/CD, but this script is a fantastic and reliable starting point.

You've Done It!

Take a moment. Open your domain in a new tab. See that beautiful little padlock? See your Next.js app, live on the internet, running on your very own server? You did that.

You've gone from a local project to a fully-fledged, secure, and robust production deployment. You configured a Linux server, set up Node.js, got your app running under a process manager, and secured it all behind an Nginx reverse proxy with a free SSL certificate. That is some serious DevOps work.

Go show it off. You've earned it.

Frequently Asked Questions

Why use a VPS instead of a platform like Vercel?

Look, Vercel is fantastic, especially for getting started quickly. But a VPS gives you ultimate control and can be more cost-effective as you scale. You can run other services on it (like a database, a cron job server, another app), and you're not locked into a specific platform's ecosystem. It's really about trading a little convenience for a whole lot of power and flexibility.

My deployment failed. What are the first things I should check?

Nine times out of ten, the answer is hiding in the logs. First, check your app's logs with pm2 logs next-app. This will show you any errors coming from Next.js itself. If the app seems to be running but you're seeing a 502 Bad Gateway error in the browser, check Nginx's error log with tail -f /var/log/nginx/error.log. It usually tells you exactly why Nginx can't connect to your app.

How do I handle database connections from my VPS?

You've got two main options here. You can use a managed database service (like PlanetScale, Neon, or a Hostinger database plan) and just put the connection string in your .env.production.local file. Or, if you want full control, you can install a database like PostgreSQL or MySQL directly on your VPS. Just be sure to configure your firewall to only allow connections from the server itself to keep it secure.

Related Articles