← Back to Articles
Deploying my own website on a home server
Next.jsRaspberry PiDevOpsNginxSSLSystemd

Deploying my own website on a home server

Deploying my own website on a home server

This article documents the complete process of deploying a Next.js application on a Raspberry Pi, making it accessible from the internet with HTTPS. We'll use cv.roman-smal.uk as our real-world example throughout this guide.

Overview

Deploying a Next.js application on a Raspberry Pi offers a cost-effective way to host personal projects and portfolios. While cloud platforms like Vercel and Netlify provide excellent hosting solutions, self-hosting on a Raspberry Pi gives you complete control over your infrastructure and valuable hands-on experience with server administration, networking, and DevOps practices.

This setup provides:

  • Full control over your hosting environment - No vendor lock-in, complete customization
  • Low cost compared to cloud hosting - One-time hardware investment (~$35-75) plus minimal electricity costs
  • Learning opportunity for DevOps and system administration - Real-world experience with Linux, systemd, Nginx, and SSL
  • Production-ready setup with SSL and reverse proxy - Professional-grade deployment with automatic restarts and security
  • Privacy and data ownership - Your data stays on your hardware, no third-party access
  • Scalable foundation - Learn concepts that apply to larger deployments and cloud infrastructure
  • Educational value - Understand how web applications work from server to browser

Whether you're a developer looking to host a personal portfolio, a student learning DevOps, or someone interested in self-hosting, this guide walks you through every step of deploying a production-ready Next.js application on a Raspberry Pi with proper SSL encryption and reverse proxy configuration.

Architecture

Our deployment uses:

  • Next.js 14 - React framework with server-side rendering
  • Systemd - Service management for automatic startup and restarts
  • Nginx - Reverse proxy and SSL termination
  • Let's Encrypt - Free SSL certificates via Certbot
  • Raspberry Pi - ARM-based single-board computer running Arch Linux

Prerequisites

  • Raspberry Pi with Arch Linux (or similar Linux distribution)
  • Node.js 18+ installed
  • Domain name (optional but recommended)
  • Router with port forwarding capability
  • Basic command-line knowledge

Step 1: Building and Transferring the Application

Build Locally

First, build your Next.js application for production:

npm run build

This creates an optimised production build in the .next directory.

Transfer to Raspberry Pi

Transfer your project to the Raspberry Pi using SCP or Git:

# Using SCP
scp -r /path/to/your/blog pi@raspberry-pi-ip:/home/pi/blog

# Or clone from Git repository
ssh pi@raspberry-pi-ip
cd /home/pi
git clone your-repo-url blog

Install Dependencies

On the Raspberry Pi:

cd /home/pi/blog
npm install --production
npm run build

Step 2: Creating a Systemd Service

Systemd ensures your application starts automatically on boot and restarts on failure.

Service Configuration

Create /etc/systemd/system/blog.service:

[Unit]
Description=Next.js Blog Application
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/blog
Environment=NODE_ENV=production
Environment=PORT=3010
ExecStart=/usr/bin/npm start
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Key points:

  • User=pi - Runs as the pi user (adjust to your username)
  • WorkingDirectory - Path to your application
  • PORT=3010 - Internal port for Next.js
  • Restart=on-failure - Automatically restarts on crashes

Enable and Start Service

sudo systemctl daemon-reload
sudo systemctl enable blog.service
sudo systemctl start blog.service
sudo systemctl status blog.service

Verify it's running:

curl http://localhost:3010

Step 3: Configuring Nginx as Reverse Proxy

Nginx acts as a reverse proxy, handling SSL termination and routing requests to your Next.js application.

Install Nginx

sudo pacman -S nginx
sudo systemctl enable nginx
sudo systemctl start nginx

Create Nginx Configuration

Create /etc/nginx/conf.d/blog.conf:

# HTTP server - redirect to HTTPS
server {
    listen 80;
    server_name cv.roman-smal.uk;

    # Redirect all HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

# HTTPS server
server {
    listen 443 ssl http2;
    server_name cv.roman-smal.uk;

    # SSL certificates (will be configured by Certbot)
    ssl_certificate /etc/letsencrypt/live/cv.roman-smal.uk/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cv.roman-smal.uk/privkey.pem;
    
    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Proxy to Next.js
    location / {
        proxy_pass http://localhost:3010;
        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;
    }
}

Update Main Nginx Config

Ensure /etc/nginx/nginx.conf includes your config:

http {
    # ... other config ...
    
    # Include configs from conf.d (must be inside http block)
    include /etc/nginx/conf.d/*.conf;
    
    # ... rest of config ...
}

Test and Reload

sudo nginx -t
sudo systemctl reload nginx

Step 4: Setting Up SSL with Let's Encrypt

Let's Encrypt provides free SSL certificates valid for 90 days with automatic renewal.

Install Certbot

sudo pacman -S certbot

Obtain Certificate

Using standalone mode (since we're not using the nginx plugin):

# Stop nginx temporarily
sudo systemctl stop nginx

# Get certificate
sudo certbot certonly --standalone -d cv.roman-smal.uk

# Start nginx
sudo systemctl start nginx

Certificates are saved to /etc/letsencrypt/live/cv.roman-smal.uk/.

Configure Auto-Renewal

Certbot creates a systemd timer for automatic renewal:

# Enable auto-renewal
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer

# Test renewal
sudo certbot renew --dry-run

Certificates automatically renew 30 days before expiration.

Step 5: Network Configuration

Router Port Forwarding

Configure your router to forward:

  • Port 80 → Raspberry Pi IP, port 80 (HTTP redirect)
  • Port 443 → Raspberry Pi IP, port 443 (HTTPS)

Firewall Configuration

Allow HTTP and HTTPS traffic:

# Using UFW
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Or using firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

DNS Configuration

Point your domain to your public IP:

  1. Add an A record: cv.roman-smal.ukyour-public-ip
  2. Wait for DNS propagation (usually 15-30 minutes)
  3. Verify: dig cv.roman-smal.uk

Step 6: Security Considerations

Production Optimizations

Update next.config.js for production:

const nextConfig = {
  reactStrictMode: true,
  
  // Disable source maps in production
  productionBrowserSourceMaps: false,
  
  // Remove console logs
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production' ? {
      exclude: ['error', 'warn'],
    } : false,
  },
  
  // Security headers
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'X-Frame-Options', value: 'DENY' },
          { key: 'X-XSS-Protection', value: '1; mode=block' },
        ],
      },
    ]
  },
}

System Updates

Keep your system updated:

sudo pacman -Syu

Troubleshooting

Service Not Starting

Check logs:

sudo journalctl -u blog.service -n 50

Common issues:

  • Build missing: Run npm run build
  • Wrong user: Update User= in service file
  • Port in use: Change PORT= environment variable

Nginx Not Proxying

Check error logs:

sudo tail -f /var/log/nginx/error.log

Verify:

  • Next.js is running: curl http://localhost:3010
  • Nginx config syntax: sudo nginx -t
  • Ports are listening: sudo ss -tlnp | grep -E ':(80|443|3010)'

SSL Certificate Issues

Verify certificate files:

sudo ls -la /etc/letsencrypt/live/cv.roman-smal.uk/

Check file permissions and paths in nginx config.

Monitoring

View Logs

# Next.js logs
sudo journalctl -u blog.service -f

# Nginx access logs
sudo tail -f /var/log/nginx/access.log

# Nginx error logs
sudo tail -f /var/log/nginx/error.log

Check Service Status

sudo systemctl status blog.service
sudo systemctl status nginx

Real-World Example

This setup is currently running at cv.roman-smal.uk, serving a Next.js portfolio and blog. The deployment includes:

  • Automatic service restarts on failure
  • SSL/TLS encryption via Let's Encrypt
  • HTTP to HTTPS redirects
  • Production-optimised Next.js build
  • Reverse proxy with proper headers

Performance Considerations

Raspberry Pi limitations:

  • Memory: Monitor with free -h
  • CPU: Check with htop
  • Storage: Monitor with df -h

For high-traffic sites, consider:

  • Cloud hosting (Vercel, Netlify)
  • More powerful hardware
  • CDN for static assets

Cost Breakdown

  • Raspberry Pi: One-time ~$35-75
  • Domain: ~$8-12/year
  • SSL Certificate: Free (Let's Encrypt)
  • Electricity: ~$2-5/year
  • Total: ~$10-17/year after initial hardware

Conclusion

Deploying Next.js on a Raspberry Pi is an excellent learning experience and cost-effective solution for personal projects. The setup provides production-ready features including SSL, automatic restarts, and reverse proxy configuration.

This deployment demonstrates practical DevOps skills including:

  • System service management
  • Reverse proxy configuration
  • SSL/TLS certificate management
  • Network configuration
  • Production optimization

For the live example, visit cv.roman-smal.uk.