
If you’ve ever worked with all-in-one solutions like Vercel or Firebase Hosting, you know the convenience: one click, one deploy, and your app is live on the web. Sounds great – but it comes at a price. As soon as you want to host more complex projects consisting of multiple components (website, backend, mobile app), you quickly hit limitations.
That’s why I chose a VPS (Virtual Private Server). A VPS gives you full control over your system. You decide which services run, how they communicate, and how you scale your app. Sure, it’s a bit more hands-on – but that’s exactly where the learning effect and flexibility come in.
Personally, I use Hostinger VPS Hosting. I’ve had very good experiences with it. Setup is quick, prices are fair, and the interface is clear even for beginners. So I can highly recommend using it for this and other personal projects. For small to medium-sized projects like ours, the cheapest plan is more than enough. You can order it directly here. Otherwise, you’ll find here an overview of other Hostinger packages. The important thing is to select a VPS hosting plan.
During your order, you’ll need to specify which operating system your VPS should use. Here, you can simply choose the latest version of Ubuntu. This is supported by all common frameworks and is perfect for our use case.
Before we get started, a quick comparison:
Vercel, Netlify & Co.
Quick setup, minimal configuration
Ideal for small projects and pure frontend hosting
Little control over server architecture, databases & security
VPS (Virtual Private Server)
You control everything: routing, ports, processes
One infrastructure for web app, mobile app, and backend
Cheaper as you scale up
Better learning curve (because you really understand how deployment works)
Of course, it always depends on your personal goals. But if you want to build a real infrastructure from various apps and a stable backend, I can only highly recommend a VPS.
I use Hostinger VPS Hosting. It’s relatively inexpensive, fast, and stable. For starters and smaller projects, the cheapest plan is more than enough. You can find it here. Before purchasing, Hostinger will ask how you want your server configured. It’s best to use a simple operating system and select Ubuntu. In the popup, you can simply choose the latest version of Ubuntu. Then you enter a few personal details and voilà – your VPS is ready.
First login via SSH
Once the VPS is set up, you’ll receive an IP address.
You’ll find this at Hostinger under VPS.
In your VPS overview at Hostinger, you’ll find the command to establish an SSH connection with root access. You can also change your SSH login password there. Use the following command to connect:
ssh root@[your server IP]Enter password → done.
Update the system
sudo apt update && sudo apt upgrade -yThis ensures all packages are up to date.
Now we’ll install nginx. Nginx is a reverse proxy web server. It’s basically the bouncer for your server. Whenever someone accesses one of your domains, nginx receives the request and forwards it to the appropriate place (e.g., your NestJS backend). This way, you can make content available on specific ports or, for example, allow access to files in a specific folder on your server. Nginx is truly an all-rounder. Later, nginx will also manage our SSL certificates (HTTPS) to ensure secure access to our domains.
Installation
sudo apt install nginx -yAfterwards, you can immediately check if nginx is running:
systemctl status nginxIf you now open your server’s IP in the browser, you should see the default nginx page.
Hooray! Your server is now accessible from the internet! 🎊 🎉
Point your domain to the server
If you already own a domain or buy one, you can easily connect it to your server. To do this, you need to adjust the DNS records in your domain dashboard. You’ll usually find this where you bought the domain, such as Hostinger.
There, you need to create a new A record. Set the name to @ and the new entry should point to your server’s IP address.
After creating the new entry, you’ll need to wait a few minutes or hours, and then it should be accessible in the browser. Again, you’ll initially just see the default nginx page.
Prepare nginx
Next, we’ll create a simple server configuration so we can later access our API (Application Programming Interface), i.e., our backend. For this, we’ll create a new folder called sites-available in the /etc/nginx directory on our server. Later, we’ll list all processes managed by nginx there. In summary, we’ll store a configuration for our NestJS app and one for our NextJS app there. The trick is that we can create as many processes as we want here, but they won’t be automatically considered by nginx. If we want nginx to consider one of the configurations, we can simply create a symlink to this config file in /etc/nginx/sites-enabled. This allows us to test different processes and roll them back at any time without having to delete them completely. So you can easily add more sites to your server.
First, we’ll create the config file for our NestJS app. Since it’s an API for a ToDo app, we’ll call it todo-api.conf. The command sudo nano opens the new file in a text editor so we can edit it.
sudo nano /etc/nginx/sites-available/todo-api.confSet the content of this file as follows:
server {
listen 80;
server_name your-domain.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;
}
}Here’s a brief explanation of the most important parts of the config file:
listen 80; --> This line tells nginx to listen on port 80. That’s the standard port for HTTP.
server_name --> This is the domain name nginx should listen to. So in this case, nginx says: “your-domain.com is called, so I’ll forward this request.” If you don’t want to use a domain, you can simply enter your server’s IP address here.
location / --> This block defines how nginx should handle requests to the root path (“/”). It sets up a reverse proxy to forward requests to a local service running (in this case) on port 3000.
proxy_pass http://localhost:3000; --> This line specifies which local service the request should be forwarded to. We need to make sure our NestJS app later runs on port 3000.
Finally, we can activate our nginx forwarding. As mentioned, we do this by creating a symlink of our config file in the sites-enabled folder. With the second command (sudo nginx -t), we check the syntax of our nginx configuration. Finally, we restart our nginx server (sudo systemctl reload nginx) and our configuration should be active.
sudo ln -s /etc/nginx/sites-available/todo.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxNow, whenever we receive requests to your-domain.com, they’ll automatically be forwarded to http://localhost:3000 on our server. Now we just need to have our NestJS app running there.
To test the nginx setup later, we need a first app. NestJS is perfect for this because it enables structured APIs in TypeScript and can integrate OpenAPI (Swagger) out of the box.
At this point, I recommend accessing your server with a code editor like Visual Studio Code. Visual Studio Code, for example, offers remote SSH connections, allowing you to develop your apps directly on the server.
NestJS is a NodeJS framework. The first step is to install NodeJS on our server. We do this as follows:
apt install nodejs npm -yTo use NestJS now, we need the NestJS CLI (Command Line Interface). We install it as follows:
npm i -g @nestjs/cliNow we can create a new NestJS project via the command line. It’s best to go to the /var/www folder by entering the following command:
cd var/wwwHere we create our NestJS app:
nest new todo-api
cd todo-apiTo get a visual confirmation, we’ll enable Swagger (OpenAPI). Open the main.ts file and adjust it as follows:
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('ToDo API')
.setDescription('API documentation for our ToDo app')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();Now start the app:
npm run start:devIf you now access your domain via your-domain.com/api or your server’s IP address via [IP address]/api in the browser, you should see the Swagger docs. Perfect! The API is running locally on the server.
As soon as you close your terminal, you won’t be able to access your domain anymore. That’s because closing the command line also stops the NestJS service. To prevent this, we’ll use PM2. PM2 is designed to run and manage multiple NodeJS services in the background. So we can easily run both our NestJS app and our NextJS app on the server without having to keep a terminal open. PM2 also allows us to disable certain processes at any time if we no longer need them, without having to delete them completely.
First, we install PM2:
cd ~
sudo npm install -g pm2Then we go to our app’s directory, if you’re not already there:
cd /var/www/todo-apiNow we can start our first PM2 process. We do this with the following command:
pm2 start npm --name "todo-api" -- startThis command creates a PM2 process named "todo-api". You can give the process any name, but I recommend choosing a descriptive name so you can easily find it later. After running the command, you should see an overview of your running PM2 processes. There’s probably only your newly created process, and it should have the status "online".
You can view your processes at any time with the following command:
pm2 statusYou can stop your process with the following command:
pm2 stop todo-apiAnd you can restart your process with the following command:
pm2 restart todo-apiIf the process is stopped, you shouldn’t be able to access the API page in the browser anymore. Play around with the different commands and test it yourself to get a better understanding of the topic.
As I mentioned earlier, nginx also manages SSL certificates for your domains. So you can easily and freely equip every domain and subdomain with an SSL certificate, making them accessible via HTTPS. All you need is certbot. Install it as follows:
apt install certbot python3-certbot-nginx -yNext, run the following command:
sudo certbot --nginxHere, first select all domains for which you want to request SSL certificates and confirm your selection with Enter. If you then look at your nginx config file again, you’ll notice a lot has changed. These changes were made automatically by certbot and ensure that all traffic to your domain is redirected to HTTPS. Your domain is now accessible and secure! 🎊 🎉
In this article, we have:
set up a VPS,
installed nginx as the “bouncer” for requests,
created a small NestJS app with Swagger,
set up PM2 to manage your NodeJS processes,
made the API cleanly accessible via our domain,
and requested an SSL certificate for our domain.
That lays the foundation. In the next part, we’ll focus on expanding the backend step by step – with authentication via Firebase Authentication and the first ToDo endpoints.
👉 Don’t forget: If you want to try a VPS yourself, I recommend Hostinger VPS Hosting.
Comments
Please sign in to leave a comment.