Deploy Bun JavaScript runtime on Linux with automatic service management through systemd and secure SSL reverse proxy configuration with Nginx for production applications.
Prerequisites
- Root or sudo access
- Domain name with DNS configured
- Basic familiarity with command line
What this solves
Bun is a fast JavaScript runtime and package manager that offers better performance than Node.js for many applications. This tutorial shows you how to install Bun on Linux, create a production-ready web application, configure it as a systemd service for automatic startup and management, and set up Nginx as a reverse proxy with SSL termination for secure access.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest security patches and package definitions.
sudo apt update && sudo apt upgrade -y
Install required dependencies
Install curl and unzip which are needed for the Bun installation script and SSL certificate management.
sudo apt install -y curl unzip nginx certbot python3-certbot-nginx
Create application user
Create a dedicated user for running the Bun application. This follows security best practices by avoiding running applications as root.
sudo useradd --system --create-home --shell /bin/bash bunapp
Install Bun runtime
Switch to the bunapp user and install Bun using the official installation script. This downloads and installs the latest stable version.
sudo -u bunapp bash -c 'curl -fsSL https://bun.sh/install | bash'
sudo -u bunapp bash -c 'echo "export PATH=\$PATH:/home/bunapp/.bun/bin" >> /home/bunapp/.bashrc'
Create sample web application
Create a directory for the application and build a simple web server to test the installation.
sudo -u bunapp mkdir -p /home/bunapp/webapp
sudo -u bunapp tee /home/bunapp/webapp/server.js > /dev/null << 'EOF'
const server = Bun.serve({
port: 3000,
hostname: '127.0.0.1',
fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/') {
return new Response(`
Bun Server
Bun JavaScript Runtime
Server is running on port 3000
Bun version: ${Bun.version}
Current time: ${new Date().toISOString()}
`, {
headers: { 'Content-Type': 'text/html' }
});
}
if (url.pathname === '/api/health') {
return Response.json({
status: 'ok',
version: Bun.version,
timestamp: new Date().toISOString()
});
}
return new Response('Not Found', { status: 404 });
},
});
console.log(Server running at http://${server.hostname}:${server.port});
EOF
Create package.json file
Add a package.json file to define the application metadata and scripts for easier management.
sudo -u bunapp tee /home/bunapp/webapp/package.json > /dev/null << 'EOF'
{
"name": "bun-webapp",
"version": "1.0.0",
"description": "Sample Bun web application",
"main": "server.js",
"scripts": {
"start": "bun run server.js",
"dev": "bun --watch server.js"
},
"dependencies": {}
}
EOF
Test the application manually
Verify that Bun can run the application correctly before creating the systemd service.
sudo -u bunapp bash -c 'cd /home/bunapp/webapp && /home/bunapp/.bun/bin/bun run server.js &'
sleep 2
curl http://127.0.0.1:3000/api/health
pkill -f "bun run server.js"
Configure systemd service
Create systemd service file
Create a systemd unit file that will manage the Bun application lifecycle, including automatic restarts and proper logging.
sudo tee /etc/systemd/system/bunapp.service > /dev/null << 'EOF'
[Unit]
Description=Bun Web Application
After=network.target
Requires=network.target
[Service]
Type=simple
User=bunapp
Group=bunapp
WorkingDirectory=/home/bunapp/webapp
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=PATH=/home/bunapp/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/home/bunapp/.bun/bin/bun run server.js
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
SyslogIdentifier=bunapp
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/home/bunapp/webapp
Resource limits
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
EOF
Enable and start the service
Reload systemd to recognize the new service, enable it for automatic startup, and start it immediately.
sudo systemctl daemon-reload
sudo systemctl enable bunapp
sudo systemctl start bunapp
Verify service status
Check that the service started successfully and is listening on the correct port.
sudo systemctl status bunapp
curl http://127.0.0.1:3000/api/health
Set up Nginx reverse proxy with SSL
Configure Nginx virtual host
Create an Nginx configuration that proxies requests to the Bun application with proper headers and security settings.
sudo tee /etc/nginx/sites-available/bunapp > /dev/null << 'EOF'
server {
listen 80;
server_name example.com www.example.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Proxy to Bun application
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;
proxy_read_timeout 86400;
proxy_redirect off;
}
# Health check endpoint
location /api/health {
proxy_pass http://127.0.0.1:3000/api/health;
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;
access_log off;
}
# Logging
access_log /var/log/nginx/bunapp_access.log;
error_log /var/log/nginx/bunapp_error.log;
}
EOF
Enable the site and test configuration
Enable the Nginx site and verify the configuration syntax before reloading.
sudo ln -s /etc/nginx/sites-available/bunapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Configure SSL with Let's Encrypt
Obtain and configure SSL certificates using Certbot. Replace example.com with your actual domain name.
sudo certbot --nginx -d example.com -d www.example.com
Configure automatic SSL renewal
Set up a cron job to automatically renew SSL certificates before they expire.
sudo crontab -e
Add this line to renew certificates twice daily:
0 12,0 * /usr/bin/certbot renew --quiet
Configure firewall rules
Allow HTTP and HTTPS traffic
Configure the firewall to allow web traffic while keeping the Bun application port (3000) internal only.
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status
Verify your setup
Test all components to ensure they're working correctly together.
# Check Bun application status
sudo systemctl status bunapp
Verify Bun is listening on port 3000
sudo netstat -tlnp | grep :3000
Test direct application access
curl http://127.0.0.1:3000/api/health
Check Nginx status
sudo systemctl status nginx
Test through reverse proxy (replace with your domain)
curl -H "Host: example.com" http://localhost/api/health
Test SSL certificate (replace with your domain)
curl -I https://example.com
Check service logs
sudo journalctl -u bunapp -f --lines 20
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Bun command not found | PATH not set correctly | Add /home/bunapp/.bun/bin to PATH in service file |
| Service fails to start | Permission issues | Check sudo journalctl -u bunapp and verify file ownership |
| 502 Bad Gateway from Nginx | Bun app not running | Check sudo systemctl status bunapp and restart if needed |
| SSL certificate errors | Domain not pointing to server | Verify DNS A record and run sudo certbot --nginx again |
| Port 3000 accessible externally | Firewall misconfiguration | Ensure only ports 80/443 are open, not 3000 |
| Application crashes on restart | Missing dependencies | Check logs and ensure all npm packages are installed |
Performance optimization
Configure Nginx caching
Add caching directives to improve performance for static assets and API responses.
# Add inside the server block, before existing location blocks
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
Cache API responses for 5 minutes
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_cache_valid 200 5m;
add_header X-Cache-Status $upstream_cache_status;
}
Reload Nginx after making changes:
sudo nginx -t && sudo systemctl reload nginx
Monitoring and maintenance
Set up log rotation
Configure logrotate to manage application and Nginx logs to prevent disk space issues.
sudo tee /etc/logrotate.d/bunapp > /dev/null << 'EOF'
/var/log/nginx/bunapp_*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 644 www-data adm
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 cat /var/run/nginx.pid
fi
endscript
}
EOF
Create health check script
Set up automated health monitoring to detect and alert on application issues.
sudo -u bunapp tee /home/bunapp/health-check.sh > /dev/null << 'EOF'
#!/bin/bash
Health check script for Bun application
HEALTH_URL="http://127.0.0.1:3000/api/health"
LOG_FILE="/home/bunapp/health-check.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
Test application health
if curl -s --max-time 10 "$HEALTH_URL" > /dev/null; then
echo "[$TIMESTAMP] Health check passed" >> "$LOG_FILE"
exit 0
else
echo "[$TIMESTAMP] Health check failed - attempting restart" >> "$LOG_FILE"
systemctl restart bunapp
sleep 5
# Test again after restart
if curl -s --max-time 10 "$HEALTH_URL" > /dev/null; then
echo "[$TIMESTAMP] Service restarted successfully" >> "$LOG_FILE"
exit 0
else
echo "[$TIMESTAMP] Service restart failed" >> "$LOG_FILE"
exit 1
fi
fi
EOF
sudo chmod +x /home/bunapp/health-check.sh
Next steps
- Configure NGINX reverse proxy with SSL termination and load balancing for high availability
- Configure Linux system services with systemctl and service management
- Monitor system resources with Netdata real-time performance dashboard
- Configure Bun application clustering with PM2 for high availability
- Set up Bun application monitoring with Prometheus and Grafana
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
DOMAIN="${1:-}"
APP_USER="bunapp"
APP_DIR="/home/$APP_USER/webapp"
LOG_FILE="/var/log/bunapp-install.log"
# Usage message
usage() {
echo "Usage: $0 <domain>"
echo "Example: $0 example.com"
exit 1
}
# Logging function
log() {
echo "$(date): $1" | tee -a "$LOG_FILE"
echo -e "${GREEN}$1${NC}"
}
error() {
echo "$(date): ERROR: $1" | tee -a "$LOG_FILE"
echo -e "${RED}ERROR: $1${NC}" >&2
}
warning() {
echo "$(date): WARNING: $1" | tee -a "$LOG_FILE"
echo -e "${YELLOW}WARNING: $1${NC}"
}
# Cleanup function
cleanup() {
if [ $? -ne 0 ]; then
error "Installation failed. Check $LOG_FILE for details"
systemctl stop bunapp 2>/dev/null || true
systemctl disable bunapp 2>/dev/null || true
userdel -r "$APP_USER" 2>/dev/null || true
fi
}
trap cleanup ERR
# Check arguments
if [ -z "$DOMAIN" ]; then
usage
fi
# Check if running as root
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root"
exit 1
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
CERTBOT_PKG="python3-certbot-nginx"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
CERTBOT_PKG="python3-certbot-nginx"
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
fi
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
CERTBOT_PKG="python3-certbot-nginx"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
else
error "Cannot detect distribution"
exit 1
fi
log "[1/10] Starting Bun installation for $DOMAIN on $ID"
# Update system packages
log "[2/10] Updating system packages..."
$PKG_UPDATE
# Install dependencies
log "[3/10] Installing dependencies..."
$PKG_INSTALL curl unzip nginx certbot $CERTBOT_PKG
# Create application user
log "[4/10] Creating application user..."
if ! id "$APP_USER" &>/dev/null; then
useradd --system --create-home --shell /bin/bash "$APP_USER"
fi
# Install Bun runtime
log "[5/10] Installing Bun runtime..."
sudo -u "$APP_USER" bash -c 'curl -fsSL https://bun.sh/install | bash'
sudo -u "$APP_USER" bash -c 'echo "export PATH=\$PATH:/home/'"$APP_USER"'/.bun/bin" >> /home/'"$APP_USER"'/.bashrc'
# Create sample web application
log "[6/10] Creating sample web application..."
sudo -u "$APP_USER" mkdir -p "$APP_DIR"
sudo -u "$APP_USER" tee "$APP_DIR/server.js" > /dev/null << 'EOF'
import { serve } from "bun";
const server = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/health') {
return new Response('OK', { status: 200 });
}
return new Response(`
<!DOCTYPE html>
<html>
<head>
<title>Bun Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; }
.info { background: #e3f2fd; padding: 15px; border-radius: 4px; margin: 20px 0; }
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Bun!</h1>
<div class="info">
<p><strong>Runtime:</strong> Bun ${Bun.version}</p>
<p><strong>Time:</strong> ${new Date().toISOString()}</p>
<p><strong>Process ID:</strong> ${process.pid}</p>
</div>
<p>Your Bun application is running successfully with systemd and Nginx reverse proxy.</p>
</div>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}
});
console.log(`Server running on port ${server.port}`);
EOF
sudo -u "$APP_USER" tee "$APP_DIR/package.json" > /dev/null << EOF
{
"name": "bun-webapp",
"version": "1.0.0",
"description": "Production Bun web application",
"main": "server.js",
"scripts": {
"start": "bun server.js",
"dev": "bun --watch server.js"
},
"dependencies": {}
}
EOF
# Create systemd service
log "[7/10] Creating systemd service..."
tee /etc/systemd/system/bunapp.service > /dev/null << EOF
[Unit]
Description=Bun Web Application
After=network.target
Wants=network.target
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR
Environment=NODE_ENV=production
Environment=PATH=/home/$APP_USER/.bun/bin:\$PATH
ExecStart=/home/$APP_USER/.bun/bin/bun server.js
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=bunapp
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$APP_DIR
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
[Install]
WantedBy=multi-user.target
EOF
chmod 644 /etc/systemd/system/bunapp.service
systemctl daemon-reload
systemctl enable bunapp
systemctl start bunapp
# Configure Nginx
log "[8/10] Configuring Nginx reverse proxy..."
if [ "$NGINX_SITES_DIR" = "/etc/nginx/sites-available" ]; then
# Debian/Ubuntu style
NGINX_CONFIG="$NGINX_SITES_DIR/$DOMAIN"
tee "$NGINX_CONFIG" > /dev/null << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
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;
proxy_read_timeout 86400;
}
location /health {
proxy_pass http://127.0.0.1:3000/health;
access_log off;
}
}
EOF
ln -sf "$NGINX_CONFIG" "$NGINX_ENABLED_DIR/$DOMAIN"
rm -f /etc/nginx/sites-enabled/default
else
# RHEL/CentOS style
NGINX_CONFIG="$NGINX_SITES_DIR/$DOMAIN.conf"
tee "$NGINX_CONFIG" > /dev/null << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
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;
proxy_read_timeout 86400;
}
location /health {
proxy_pass http://127.0.0.1:3000/health;
access_log off;
}
}
EOF
fi
chmod 644 "$NGINX_CONFIG"
nginx -t
systemctl enable nginx
systemctl restart nginx
# Configure firewall
log "[9/10] Configuring firewall..."
if command -v ufw &> /dev/null; then
ufw --force enable
ufw allow ssh
ufw allow 'Nginx Full'
elif command -v firewall-cmd &> /dev/null; then
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Create health check script
tee /home/$APP_USER/health-check.sh > /dev/null << 'EOF'
#!/bin/bash
HEALTH_URL="http://localhost:3000/health"
LOG_FILE="/var/log/bunapp-health.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
if curl -s --max-time 10 "$HEALTH_URL" > /dev/null; then
echo "[$TIMESTAMP] Health check passed" >> "$LOG_FILE"
exit 0
else
echo "[$TIMESTAMP] Health check failed - attempting restart" >> "$LOG_FILE"
systemctl restart bunapp
sleep 5
if curl -s --max-time 10 "$HEALTH_URL" > /dev/null; then
echo "[$TIMESTAMP] Service restarted successfully" >> "$LOG_FILE"
exit 0
else
echo "[$TIMESTAMP] Service restart failed" >> "$LOG_FILE"
exit 1
fi
fi
EOF
chmod 755 /home/$APP_USER/health-check.sh
chown $APP_USER:$APP_USER /home/$APP_USER/health-check.sh
# Verification
log "[10/10] Verifying installation..."
sleep 3
if systemctl is-active --quiet bunapp; then
log "✓ Bun service is running"
else
error "✗ Bun service is not running"
exit 1
fi
if systemctl is-active --quiet nginx; then
log "✓ Nginx service is running"
else
error "✗ Nginx service is not running"
exit 1
fi
if curl -s --max-time 10 "http://localhost:3000/health" > /dev/null; then
log "✓ Application health check passed"
else
error "✗ Application health check failed"
exit 1
fi
log "Installation completed successfully!"
log "Application is available at: http://$DOMAIN"
log "To enable SSL, run: certbot --nginx -d $DOMAIN -d www.$DOMAIN"
log "Health check script: /home/$APP_USER/health-check.sh"
log "Service management: systemctl {start|stop|restart|status} bunapp"
Review the script before running. Execute with: bash install.sh