INNOVATECH GROUP
Install this app for quick access.
Highly Technical
Published 17 Apr 2026

Deploying a Managed Laravel Application on Hetzner Cloud via SSH

12 min Developers
Share

Executive Summary

This article covers the complete workflow for deploying a Laravel application to a Hetzner Cloud server instance via SSH: provisioning the server with an SSH key, hardening the initial configuration, installing the LEMP stack (Nginx, PHP 8.2+, MySQL/MariaDB, Composer, Node.js), running the Laravel production deployment checklist, configuring a systemd-managed queue worker, and issuing an SSL certificate with Certbot. The Hetzner Cloud console is accessed directly — there is no INNOVATECH portal integration for server provisioning.

This guide walks through the complete process of provisioning a Hetzner Cloud server instance and deploying a Laravel application to it via SSH. It covers server provisioning, initial hardening, LEMP stack installation, Laravel deployment, queue worker configuration, and SSL setup.

The Hetzner Cloud console is accessed directly at console.hetzner.cloud. The INNOVATECH GROUP portal does not manage or provision Hetzner Cloud instances — your portal hosting/cloud service detail page provides reference information only.

Prerequisites

  • A Hetzner Cloud account with an active project at console.hetzner.cloud
  • An SSH key pair on your local machine (or the ability to generate one)
  • A domain name with DNS pointing to the server IP (for SSL)
  • Your Laravel application in a Git repository
  • Familiarity with Linux command-line administration, SSH, and basic server operations

Step 1: Generate an SSH Key Pair

If you do not already have an SSH key pair on your local machine:

ssh-keygen -t ed25519 -C "deploy@yourdomain.co.za"

Accept the default file location (~/.ssh/id_ed25519) or specify a custom path. Set a passphrase for additional security.

Display the public key to copy it:

cat ~/.ssh/id_ed25519.pub

Step 2: Provision the Hetzner Cloud Server

  1. Log in to the Hetzner Cloud console at console.hetzner.cloud.
  2. Select your project and click Add Server.
  3. Configure the server:
    • Location: Choose the data centre closest to your target audience (e.g. fsn1 for Falkenstein, Germany, or hel1 for Helsinki).
    • Image: Ubuntu 22.04 LTS (or the current Ubuntu LTS available).
    • Type: Select a server type based on your application's resource requirements. CX22 (2 vCPU, 4 GB RAM) is a reasonable starting point for a Laravel application.
    • SSH Keys: Click Add SSH Key, paste your public key from Step 1, give it a label, and save. Ensure it is selected for this server.
    • Firewall: Optionally assign a Hetzner Cloud firewall group. You can also configure host-level firewall rules after provisioning (covered in Step 4).
    • Name: Give the server a descriptive name (e.g. laravel-prod-01).
  4. Click Create & Buy Now to provision the server.

Note the server's public IPv4 address once provisioning is complete.

Step 3: Initial SSH Connection

Connect to the server as root using the SSH key:

ssh root@YOUR_SERVER_IP

Verify the connection succeeds. All subsequent commands in this guide are executed on the server via SSH unless otherwise noted.

Step 4: Server Hardening

Create a Deploy User

Running your application as root is a security risk. Create a dedicated deploy user:

adduser deploy
usermod -aG sudo deploy

Copy the SSH authorised key to the new user:

mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Verify you can log in as the deploy user from your local machine before proceeding:

ssh deploy@YOUR_SERVER_IP

Disable Root SSH Login

Edit the SSH daemon configuration:

sudo nano /etc/ssh/sshd_config

Set the following:

PermitRootLogin no
PasswordAuthentication no

Restart SSH:

sudo systemctl restart sshd

Configure UFW Firewall

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable

Confirm the rules:

sudo ufw status verbose

Step 5: Install the LEMP Stack

System Updates

sudo apt update && sudo apt upgrade -y

Install Nginx

sudo apt install -y nginx
sudo systemctl enable nginx

Install PHP 8.2+ and Required Extensions

sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
sudo apt install -y php8.2-fpm php8.2-cli php8.2-common php8.2-mysql \
    php8.2-xml php8.2-mbstring php8.2-curl php8.2-zip php8.2-gd \
    php8.2-bcmath php8.2-intl php8.2-readline php8.2-redis

Verify the installation:

php -v

Install MySQL or MariaDB

sudo apt install -y mariadb-server
sudo systemctl enable mariadb
sudo mysql_secure_installation

Create the application database and user:

sudo mysql -u root -p
CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel'@'localhost' IDENTIFIED BY 'YOUR_SECURE_PASSWORD';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Replace YOUR_SECURE_PASSWORD with a strong, unique password.

Install Composer

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version

Install Node.js (for Asset Compilation)

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -v && npm -v

Step 6: Deploy the Laravel Application

Clone the Repository

sudo mkdir -p /var/www/laravel-app
sudo chown deploy:deploy /var/www/laravel-app
cd /var/www/laravel-app
git clone git@github.com:your-org/your-app.git .

Install Dependencies

composer install --no-dev --optimize-autoloader
npm ci && npm run build

Configure the Environment

cp .env.example .env
nano .env

Set production values:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.co.za

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_app
DB_USERNAME=laravel
DB_PASSWORD=YOUR_SECURE_PASSWORD

Generate the application key:

php artisan key:generate

Run the Production Deployment Checklist

php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

Set File Permissions

sudo chown -R deploy:www-data /var/www/laravel-app
sudo chmod -R 775 storage bootstrap/cache

Step 7: Configure Nginx

Create an Nginx server block:

sudo nano /etc/nginx/sites-available/laravel-app
server {
    listen 80;
    server_name yourdomain.co.za www.yourdomain.co.za;
    root /var/www/laravel-app/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Enable the site and test the configuration:

sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 8: Configure the Queue Worker

Create a systemd service file for the Laravel queue worker:

sudo nano /etc/systemd/system/laravel-queue.service
[Unit]
Description=Laravel Queue Worker
After=network.target

[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/laravel-app
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-time=3600
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable laravel-queue
sudo systemctl start laravel-queue
sudo systemctl status laravel-queue

Step 9: Install SSL with Certbot

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.co.za -d www.yourdomain.co.za

Certbot will modify your Nginx configuration to serve HTTPS and set up automatic certificate renewal. Verify the renewal timer:

sudo certbot renew --dry-run

Step 10: Validate the Deployment

  1. Visit https://yourdomain.co.za in a browser. You should see your Laravel application.
  2. Check that HTTPS is working correctly (padlock icon, no mixed content warnings).
  3. Test the queue worker by dispatching a test job and verifying it processes.
  4. Check the Laravel log for errors:
    tail -50 /var/www/laravel-app/storage/logs/laravel.log
    

Common Failure Modes

Symptom Likely Cause Action
502 Bad Gateway PHP-FPM not running or socket path mismatch Verify sudo systemctl status php8.2-fpm and check the fastcgi_pass path in Nginx config
"Permission denied" on storage/logs Incorrect file ownership Run sudo chown -R deploy:www-data storage bootstrap/cache
Blank page with no error APP_DEBUG=false hides errors Check storage/logs/laravel.log for the actual error
CSS/JS assets not loading Assets not compiled or Vite manifest missing Run npm ci && npm run build on the server
Queue jobs not processing systemd service stopped or crashed Check sudo systemctl status laravel-queue and view logs with journalctl -u laravel-queue

Security Considerations

  • Keep APP_DEBUG=false in production. Debug mode exposes environment variables, database credentials, and stack traces.
  • Use a strong, unique database password. Do not reuse passwords across services.
  • Regularly apply system updates: sudo apt update && sudo apt upgrade.
  • Consider restricting SSH access by IP in your UFW rules if your team connects from known addresses.
  • The .env file contains sensitive credentials — ensure it is not accessible via the web server (the Nginx location ~ /\. rule above blocks this).
  • Rotate the APP_KEY only if you understand the implications for encrypted data in the database.

When to Contact INNOVATECH GROUP Support

Open a support ticket if:

  • You need guidance on selecting a Hetzner Cloud server type for your application's workload
  • You encounter networking issues between your Hetzner Cloud server and INNOVATECH-managed services
  • You need help configuring DNS records on a domain managed through the INNOVATECH GROUP portal to point to your Hetzner Cloud server
  • You require managed server administration beyond self-service deployment

Was this helpful?

Let us know if this article answered your question.

We use cookies to keep you signed in and remember your preferences. By continuing, you agree to our Privacy policy.