Skip to content

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-ip

Install 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 --version

Install PM2 process manager ​

PM2 is used to daemonize Node.js processes, supporting automatic restart and startup on boot:

bash
sudo npm install -g pm2

3. 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 --production

5. 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=...
EOF

6. 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 command

Common PM2 commands ​

bash
pm2 status      # View application status
pm2 logs        # View logs
pm2 restart     # Restart the app
pm2 stop        # Stop application
pm2 monit       # Monitoring resources

7. Configure Nginx reverse proxy ​

Install Nginx ​

bash
sudo apt-get update
sudo apt-get install -y nginx

Basic configuration ​

bash
sudo vim /etc/nginx/sites-available/myapp

Note

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 nginx

Note

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 enable

9. 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-nginx

Obtain and configure certificates ​

bash
# Automatically obtain certificates and configure Nginx
sudo certbot --nginx -d your-domain.com

# Test automatic renewal
sudo certbot renew --dry-run

Verify certificate configuration ​

bash
# Check certificate status
sudo certbot certificates

Let'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:

TypeNameContentAgent Status
Ayour-domain.comServer 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.com and *.your-domain.com

  • Certificate 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.pem

Step 3: Configure Nginx ​

bash
sudo vim /etc/nginx/sites-available/your-domain

Note

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 :443

Note

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 200

Solution comparison ​

CharacteristicsLet's EncryptCloudflare
Configuration complexitymediumSimple
Certificate renewalAutomatic (requires certbot service)Fully automatic
CDN acceleration❌✅
DDoS protection❌✅
Server resource usageCertificate files need to be managed.No additional space required
Global access speedDepends on the server locationEdge node acceleration
CostFreeFree

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-ip

11. 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 sharp

Font 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.gz

Grant execute permission and use:

bash
chmod +x deploy.sh
./deploy.sh

Conclusion ​

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 -f

Summary

  • Manage Node.js processes with PM2

  • Use Nginx as a reverse proxy

  • Choose Let's Encrypt for independent deployment

  • Use Cloudflare as the CDN

Last updated on11/07/2026, 02:16 PM
@fishfinal | @fishfinals | [email protected]