---
title: Manual Installation
description: "Use a manual installation when the automated installer cannot be used — an existing server with its own nginx or Apache configuration, a container image, or a distribution the installer does not recognise. Every step below mirrors what `install.sh` does,…"
url: "https://rackhelm.com/documentation/manual-installation/"
updated: "2026-09-24"
category: "Installation & Server Setup"
---

# Manual Installation

Use a manual installation when the automated installer cannot be used — an existing server with its own nginx or Apache configuration, a container image, or a distribution the installer does not recognise. Every step below mirrors what `install.sh` does, so the result is the same deployment, just assembled by hand.

## Before you start

- PHP 8.2 or newer with the extensions listed in [System requirements](/docs/system-requirements/), and the **ionCube Loader active for both the FPM and CLI SAPIs**. Nothing will run without it — the shipped build is encoded.
- Composer 2, Node.js 22.x with npm, MySQL or MariaDB, and Redis.
- The RackHelm release archive and your licence key.

> **Warning:** Verify the Loader before anything else with `php -v` — the output must mention ionCube. If it does not, every Composer and Artisan command below fails with a cryptic parse error.

## 1. Put the files in place

Extract the archive to the directory the web server will serve from. The installer uses `/var/www/rackhelm` and the rest of this article assumes that path.

```bash
mkdir -p /var/www/rackhelm
tar -xzf rackhelm-release.tar.gz -C /var/www/rackhelm --strip-components=1
cd /var/www/rackhelm
```

If you are reusing a directory that previously held RackHelm, delete the compiled caches before you continue — a manifest generated while development dependencies were present will fatal on boot under a `--no-dev` install:

```bash
rm -f bootstrap/cache/packages.php bootstrap/cache/services.php bootstrap/cache/config.php
```

## 2. Install the PHP dependencies

```bash
composer install --no-dev --optimize-autoloader
```

`--no-dev` matters: the development packages are not shipped or needed in production, and installing them changes the discovered provider list.

## 3. Build the front end

```bash
npm install
npm run build
```

`npm run build` runs Vite and writes the compiled CSS and JavaScript into `public/build`. Without it every page loads unstyled and the admin area's interactive components do not work.

## 4. Create the database

Create an empty database and a user with full privileges on it:

```sql
CREATE DATABASE `rackhelm`;
CREATE USER 'rackhelm_user'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON `rackhelm`.* TO 'rackhelm_user'@'localhost';
FLUSH PRIVILEGES;
```

## 5. Write the environment file

```bash
cp .env.example .env
php artisan key:generate
```

`key:generate` needs `.env` to be writable by the user running it. If you would rather not grant that, generate the key yourself and paste it in:

```bash
echo "APP_KEY=base64:$(openssl rand -base64 32)"
```

Then edit `.env`. Check each key below: edit the line if it is already there, and append it if it is not — several of these are not in `.env.example` at all. Where a key ends up defined twice, the **first** occurrence wins, so delete duplicates rather than adding another line at the bottom.

```ini
APP_ENV=production
APP_DEBUG=false
APP_URL=https://portal.example.com
ASSET_URL=https://portal.example.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=rackhelm
DB_USERNAME=rackhelm_user
DB_PASSWORD=a-long-random-password

CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
REDIS_PREFIX=rackhelm-database-
HORIZON_PREFIX=rackhelm_horizon:

RACKHELM_LICENSE_KEY="your-licence-key"
```

Every key is explained in [The .env file explained](/docs/env-file-reference/). Two are easy to miss: `APP_URL` must be the exact public URL, because RackHelm only trusts requests whose `Host` matches it; and the two Redis prefixes should be pinned literally rather than left to derive from `APP_NAME`.

## 6. Migrate and seed

```bash
php artisan migrate --seed --force
```

The seeder creates roles, the Super Admin account, countries, a support department, company settings, the shipped content pages, the system schedule rows, the report definitions and the site templates. Watch the output: when the Super Admin is created for the first time it prints **Owner User Created!** followed by **Email:** and **Password:** lines. The address is the fixed seed value `admin@orbi-hosting.com`; the password is 16 random characters and is never displayed again. Change both as soon as you sign in.

![Terminal output from the database seeder showing Owner User Created with the seeded e-mail address and a generated password](/wp-content/uploads/rackhelm-docs/installation-seeder-admin-credentials.png)
The seeder prints the Super Admin credentials once; copy them before you clear the terminal.

> **Tip:** If you only want the schema and not the seed data, run `php artisan migrate --force`. You will then have no Super Admin account and no seeded settings, so this is only sensible when restoring an existing database.

## 7. Link storage and cache the views

```bash
php artisan storage:link
php artisan view:cache
```

`storage:link` creates the `public/storage` symlink that serves uploaded logos and page assets.

> **Warning:** Do not run `php artisan optimize`, `route:cache` or `config:cache` on an ionCube-encoded build. Route caching serialises closure-based routes and closure serialisation reads PHP source that the encoder removes, so the cached routes fail at runtime with `Call to a member function bindTo() on null`. Config caching would freeze the licence key, which is read from `.env` at runtime. Only view caching is safe.

## 8. Set ownership and permissions

RackHelm's security model keeps the application source read-only to the PHP runtime user, so a file-write bug in the application cannot rewrite `public/index.php` or a Blade template. Replicate it (substitute `nginx` for `www-data` on RHEL):

```bash
chown -R root:www-data /var/www/rackhelm
chmod -R u=rwX,g=rX,o= /var/www/rackhelm

chown -R www-data:www-data storage bootstrap/cache
chmod -R u=rwX,g=rX,o= storage bootstrap/cache

chown root:www-data .env
chmod 640 .env
```

## 9. Configure the web server

Point the document root at `/var/www/rackhelm/public` and send everything that is not a real file to `index.php`. A ready-made nginx server block, including the security headers and the PHP-FPM guard the installer applies, is in [Web server, SSL and HTTPS](/docs/web-server-ssl-and-https/). For Apache, enable `mod_rewrite` and allow `.htaccess` overrides on the directory — RackHelm ships a complete `public/.htaccess` with the rewrite rules and security headers already in it.

Install a TLS certificate before you try to log in. The session cookie is HTTPS-only, so a plain-HTTP portal rejects every login attempt with a 419 error.

## 10. Start the queue worker and the scheduler

Nothing in RackHelm provisions, invoices or e-mails without a running queue worker. Create a Supervisor program for Horizon and a one-line cron entry for the scheduler, both covered step by step in [Queue workers, Horizon and the scheduler](/docs/queue-workers-and-scheduler/).

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Parse errors or blank pages immediately after install | The ionCube Loader is not active. Check `php -v` for both the CLI and the FPM SAPI — `php -i | grep ioncube` under FPM is not the same check as the CLI one. |
| `Class LaravelPailPailServiceProvider not found` | A stale `bootstrap/cache/packages.php` from a build that had development dependencies. Delete the three files in step 1 and re-run `composer install --no-dev`. |
| `400 Bad Request` on every page | `APP_URL` does not match the hostname you are visiting. RackHelm trusts only the host in `APP_URL`; fix it and clear nothing else — the value is read at runtime. |
| Login returns `419 Page Expired` | No TLS. The session cookie is marked Secure, so the browser never stores it over plain HTTP. |
| Pages load without styling | `npm run build` was not run, or `public/build` is not readable by the web-server user. |
| `Licence validation error: No RackHelm licence key configured.` | `RACKHELM_LICENSE_KEY` is empty or missing from `.env`. See [Licensing and activation](/docs/licensing-and-activation/). |
| Uploaded logos and page images 404 | `php artisan storage:link` was not run, or the symlink was lost when the tree was re-copied. |
