English
Complete Guide to Deploying a Nuxt Application to an Ubuntu 22.04 Server
Applicable scenarios
This article is intended for developers who have completed a local build of a Nuxt project and need to deploy it to a production environment.
💡 About the build method
This article focuses on Deployment phase The build can be completed locally or through CI (such as GitHub Actions, GitLab CI, etc.). Regardless of which build method is used, the deployment steps in this article are fully applicable.
Preface
Nuxt project execution pnpm build Afterwards, it will generate .output directory, which contains server code that can be run directly. This article will explain in detail how to deploy the build artifacts to an Ubuntu 22.04 server and provide two SSL certificate configuration options for you to choose from.
0. Preliminary Preparation
On the remote server where the Nuxt project is to be deployed, create a working directory under the root account's home directory, for example ~/workspace, so that it is convenient for us to clearly specify the destination path of the file after upload when uploading files in the terminal.
1. Prepare deployment files
In the local project root directory, the build artifacts are located at .output In the folder.
💡 About the packaging directory
For deployment convenience, only ... should be .output Table of Contents All files under Pack into deploy.tar.gz in the archive file rather than packaged .output the directory itself. Because if what is obtained after decompressing on the server is .output The directory will cause the path to become /var/www/myapp/.output, causing ambiguity and unfriendliness in directory management.
bash
# Method 1: Package after entering the .output directory
cd .output
tar -czf ../deploy.tar.gz .
cd ..
# Method 2: Package with a single command (recommended)
tar -czf deploy.tar.gz -C .output .
# Upload to the server (replace with your server information)
scp deploy.tar.gz root@your-server-ip:/root/workspace/2. Server Environment Configuration
Connect to Ubuntu 22.04 server via SSH:
bash
ssh root@your-server-ipInstall Node.js
Node.js 20 or later is recommended:
bash
# Use the official NodeSource repository.
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify installation
node --version # Should display v20.x
npm --versionInstall PM2 process manager
PM2 is used to daemonize Node.js processes, supporting automatic restart and startup on boot:
bash
sudo npm install -g pm23. Extract and deploy
bash
# Create application directory
sudo mkdir -p /var/www/myapp
sudo chown -R $USER:$USER /var/www/myapp
# Extract the file
tar -xzf /root/workspace/deploy.tar.gz -C /var/www/myapp/
# Enter directory
cd /var/www/myapp/
# View directory structure (optional)
ls -la
# The output should include the server/ and public/ directories.4. Check and install dependencies (optional)
bash
# The .output directory already contains the files required to run, so it can usually be run directly.
# If it prompts that dependencies are missing, then run:
npm install --production5. Configure environment variables
bash
# Create .env file
cat > .env << 'EOF'
NODE_ENV=production
PORT=3000
NUXT_PUBLIC_SITE_URL=https://your-domain.com
# Add other variables as needed for your project.
# DATABASE_URL=...
# API_BASE_URL=...
EOF6. Use PM2 to start the application
bash
# Launch the application
pm2 start server/index.mjs \
--name "myapp" \
--node-args="--env-file=.env"
# Save the PM2 configuration (for auto-start on boot)
pm2 save
# Generate boot autostart script
pm2 startup
# The command output by executing the above commandCommon PM2 commands
bash
pm2 status # View application status
pm2 logs # View logs
pm2 restart # Restart the app
pm2 stop # Stop application
pm2 monit # Monitoring resources7. Configure Nginx reverse proxy
Install Nginx
bash
sudo apt-get update
sudo apt-get install -y nginxBasic configuration
bash
sudo vim /etc/nginx/sites-available/myappNote
If what you are currently deploying is the main site of a domain, you can overwrite it directly./etc/nginx/sites-available/default
nginx
server {
listen 80;
server_name your-domain.com;
access_log /var/log/nginx/myapp-access.log;
error_log /var/log/nginx/myapp-error.log;
# Configuration for obtaining the real IP (additional configuration is required if using Cloudflare)
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_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;
}
# Static asset caching strategy
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
}Enable site
bash
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxNote
If it is edited /etc/nginx/sites-available/default If it is a configuration file, no execution is required.sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ The command above!
8. Configure the firewall
bash
# Basic port opening
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS (if needed)
# Enable firewall
sudo ufw enable9. Configure SSL certificate
Here are two options; you can choose according to the actual situation.
Option 1: Use Let's Encrypt (standalone deployment)
Applicable scenarios: The server provides services externally on its own, without going through a CDN proxy.
Install Certbot
bash
sudo apt-get install -y certbot python3-certbot-nginxObtain and configure certificates
bash
# Automatically obtain certificates and configure Nginx
sudo certbot --nginx -d your-domain.com
# Test automatic renewal
sudo certbot renew --dry-runVerify certificate configuration
bash
# Check certificate status
sudo certbot certificatesLet's Encrypt certificates are valid for 90 days, and Certbot will automatically renew them.
Option 2: Use Cloudflare Certificate (CDN + SSL)
Applicable scenarios: The domain is already hosted on Cloudflare, and I hope to get CDN acceleration and DDoS protection.
Cloudflare DNS Configuration
Set the DNS record to proxy mode in the Cloudflare dashboard:
| Type | Name | Content | Agent Status |
|---|---|---|---|
| A | your-domain.com | Server public IP | ✅ Proxied (orange cloud) |
Step 1: Generate a Cloudflare Origin CA certificate
Log in to the Cloudflare dashboard and go to SSL/TLS → Origin Server
Click Create Certificate
Keep default settings:
Private key type: RSA(2048)
Hostnames: Add
your-domain.comand*.your-domain.comCertificate Validity: 15 years (recommended)
Click Create
Important: Copy the certificate content and private key content that pop up, and save them separately (the private key is displayed only once!)
Step 2: Install the certificate on the server
bash
# Create SSL directory
sudo mkdir -p /etc/nginx/ssl
# Save certificate (paste the content from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----)
sudo nano /etc/nginx/ssl/your-domain.pem
# Save private key (paste the content from -----BEGIN PRIVATE KEY----- to -----END PRIVATE KEY-----)
sudo nano /etc/nginx/ssl/your-domain.key
# Set permissions (Important!)
sudo chmod 600 /etc/nginx/ssl/your-domain.key
sudo chmod 644 /etc/nginx/ssl/your-domain.pemStep 3: Configure Nginx
bash
sudo vim /etc/nginx/sites-available/your-domainNote
If you are currently deploying the main site of a domain, you can directly overwrite it./etc/nginx/sites-available/default
nginx
# HTTP redirect to HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS configuration (using Cloudflare Origin CA certificate)
server {
listen 443 ssl http2;
server_name your-domain.com;
# Cloudflare Origin CA Certificate
ssl_certificate /etc/nginx/ssl/your-domain.pem;
ssl_certificate_key /etc/nginx/ssl/your-domain.key;
# SSL optimized configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
access_log /var/log/nginx/your-domain-access.log;
error_log /var/log/nginx/your-domain-error.log;
# Cloudflare IP address ranges (used to obtain the real client IP)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
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_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 https;
}
# Static asset caching strategy
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
}Step 4: Enable the site and restart Nginx
bash
# Enable site
sudo ln -s /etc/nginx/sites-available/your-domain /etc/nginx/sites-enabled/
# Test configuration syntax
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
# Verify whether port 443 is listening.
sudo netstat -tlnp | grep :443Note
If it was edited /etc/nginx/sites-available/default If it is a configuration file, no execution is required.sudo ln -s /etc/nginx/sites-available/your-domain /etc/nginx/sites-enabled/ The command above!
Step 5: Configure Cloudflare SSL/TLS mode
Return to the Cloudflare console:
Enter SSL/TLS→Overview
Set the encryption mode to Strict (Full strict)
Optional: Enable Always use HTTPS and Automatic HTTPS Rewrite
Verify whether HTTPS is in effect
bash
# Test HTTPS access
curl -I https://your-domain.com
# Should return HTTP/2 200Solution comparison
| Characteristics | Let's Encrypt | Cloudflare |
|---|---|---|
| Configuration complexity | medium | Simple |
| Certificate renewal | Automatic (requires certbot service) | Fully automatic |
| CDN acceleration | ❌ | ✅ |
| DDoS protection | ❌ | ✅ |
| Server resource usage | Certificate files need to be managed. | No additional space required |
| Global access speed | Depends on the server location | Edge node acceleration |
| Cost | Free | Free |
10. Post-deployment verification
Check service status
bash
# Check PM2
pm2 status
# Check Nginx
sudo systemctl status nginx
# Check port listening
sudo netstat -tlnp | grep -E "(3000|80|443)"Access test
bash
# Local test
curl http://localhost:3000
# Remote testing (in your local terminal)
curl http://your-server-ip11. Troubleshooting
Sharp binary file compatibility
The build log indicates that it includes the darwin-x64 sharp binary files, which may need to be rebuilt on Ubuntu:
bash
cd /var/www/myapp/.output
npm rebuild sharpFont download timed out
If the server is in mainland China, font downloads may time out. Solution:
Use a domestic font CDN mirror
Download the font locally and configure it as a local resource.
Large file build warning
The chunk size warnings appearing in the logs can be optimized in the following ways:
javascript
// nuxt.config.ts
export default defineNuxtConfig({
build: {
chunkSizeWarningLimit: 1000, // Increase limit (unit: kB)
},
// Or use dynamic imports to optimize code splitting
})12. Set up an automated deployment script (optional)
Create deploy.sh The script runs locally:
bash
#!/bin/bash
# Configuration variables
SERVER="root@your-server-ip"
APP_PATH="/var/www/myapp"
PM2_NAME="myapp"
# Build
echo "📦 Building project..."
pnpm build
# pack
echo "📁 Packaging build output..."
tar -czf deploy.tar.gz -C .output .
# Upload
echo "📤 Uploading to server..."
scp deploy.tar.gz $SERVER:$APP_PATH/
# Remote deployment
echo "🚀 Deploying on server..."
ssh $SERVER "cd $APP_PATH && \
tar -xzf deploy.tar.gz && \
pm2 reload $PM2_NAME && \
rm deploy.tar.gz && \
echo '✅ Deployment complete!'"
# Clean up
rm deploy.tar.gzGrant execute permission and use:
bash
chmod +x deploy.sh
./deploy.shConclusion
After deployment is complete, visit your domain to see the website. It is recommended to first test with HTTP to ensure everything works properly, and then configure the SSL certificate.
If you encounter any problems, you can check the following logs:
bash
# PM2 logs
pm2 logs myapp
# Nginx logs
sudo tail -f /var/log/nginx/myapp-error.log
# System log
journalctl -u nginx -fSummary
Manage Node.js processes with PM2
Use Nginx as a reverse proxy
Choose Let's Encrypt for independent deployment
Use Cloudflare as the CDN