Introduction
A Virtual Private Server (VPS) is the ultimate developer playground—a blank canvas that offers total control but also demands responsibility. It's where your code meets the real world.
In this guide, I'll walk you through my personal process for taking a high-performance Bun.js API from a Git repository to a live, production-ready application. We'll cover everything from setting up the environment to configuring a reverse proxy with Nginx and ensuring our app never goes down.
Our stack:
- Backend: Bun.js
- Server: A Linux VPS running a
dnf-based OS (like Fedora, CentOS, or RHEL) - Process Manager: PM2
- Web Server / Reverse Proxy: Nginx
Let's get started.
Step 1: Setting Up the Server Environment
Before we can run our app, we need to install the core tools.
First, let's install Bun, the star of our show. The official installer makes it simple.
# Install Bun using the recommended curl script curl -fsSL https://bun.sh/install | bash # Add Bun to your shell's path source /home/your-user/.bashrc # Verify the installation bun -v
Next, we'll need Git to pull our code from its repository. We'll also install Node.js, as Bun can leverage its ecosystem for compatibility with some native modules.
# Install Git and Node.js using the dnf package manager sudo dnf install git nodejs -y
Step 2: Getting Your Code on the Server
With the environment ready, let's deploy our application code.
Clone your project from its Git repository. Be sure to replace the URL with your own.
# Clone your application git clone https://github.com/your-username/my-bun-api.git # Navigate into the project directory cd my-bun-api # Install dependencies using Bun's fast package manager bun install
An Important Note on Secrets: Never commit API keys, database credentials, or other secrets to your Git repository. The best practice is to manage them in a separate file and securely copy them to the server.
You can use the scp (secure copy) command from your local machine's terminal to transfer your secrets file.
# Example from a local Windows/Mac/Linux machine scp "path/to/your/secrets.json" your-user@your_server_ip:/home/your-user/my-bun-api/
Before we move on, let's run the app in development mode to ensure everything installed correctly and there are no immediate errors.
# Run a quick test bun run dev
Once you confirm it runs, you can stop it with Ctrl+C.
Step 3: Keeping It Running with PM2
Running bun run dev is great for testing, but it's not for production. If you close your terminal or the app crashes, it will stop. We need a process manager to keep it running 24/7. PM2 is a fantastic choice.
# Install PM2 globally using Bun bun install -g pm2 # Start your application with PM2 # We use a 'start' script from package.json for production pm2 start bun --name my-bun-api -- run start # Now, tell PM2 to save the process list and have it # automatically restart on server reboots. pm2 save
PM2 will now monitor your application, restart it if it crashes, and manage logs.
Step 4: Facing the World with Nginx
Our app is running, but it's likely on an internal port like 4011. We need a way to expose it to the world securely on the standard web ports (80 for HTTP and 443 for HTTPS). This is the job of a reverse proxy, and we'll use Nginx.
First, let's create the standard directory structure for our site configurations.
sudo mkdir -p /etc/nginx/sites-available sudo mkdir -p /etc/nginx/sites-enabled
Now, create a configuration file for your domain in the sites-available directory.
sudo nano /etc/nginx/sites-available/your-domain.com
Paste the following configuration into the file. This setup does two things:
- Redirects all insecure
httptraffic to securehttpstraffic. - Passes all requests for your domain to the Bun app running on its local port.
# This server block catches HTTP traffic on port 80... server { listen 80; server_name your-domain.com www.your-domain.com; # ...and permanently redirects it to the HTTPS version. return 301 https://$server_name$request_uri; } # This is the main server block for your application. server { listen 443 ssl; server_name your-domain.com www.your-domain.com; # Point to your SSL certificates (we assume you've set them up with Certbot) ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # Modern SSL protocols ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { # This is the magic part: forwarding traffic to our Bun app proxy_pass http://localhost:4011; # IMPORTANT: Match your app's port proxy_http_version 1.1; # These headers are crucial for passing information to your app proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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; # Optional: Add CORS headers if your API is consumed by a web front-end add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type'; if ($request_method = 'OPTIONS') { return 204; } } }
Now, enable this configuration by creating a symbolic link to it in the sites-enabled directory.
sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/
Finally, let's test the Nginx configuration for syntax errors and reload it to apply the changes.
# Test the config sudo nginx -t # If the test is successful, reload Nginx sudo systemctl reload nginx
Conclusion
And that's it! You've successfully taken a Bun.js application from a Git repository to a fully-featured, secure, and resilient production deployment. You've prepared the server, deployed the code, set up a process manager to ensure uptime, and configured a professional-grade reverse proxy.
Your application is now live at https://your-domain.com, ready to serve users. Happy coding!