Manual Installation
Install NixStream on Ubuntu 22.04 without Docker
Manual install is for when you want full control over the stack. You run PHP, Nginx, MySQL, Redis, FFmpeg, and background workers yourself. Ubuntu 22.04 is the recommended baseline.
Shared hosting without shell access or queue workers will not work. NixStream needs Redis-backed queue workers for encoding and transcription jobs.
Want a faster path? Use the Docker install instead. This guide covers the same setup as the Docker image, on bare metal.
Before you start
You will need a VPS or dedicated server with:
- Ubuntu 22.04 LTS
- Root or sudo access
- At least 4 CPU cores and 8 GB RAM
- 100 GB SSD (more if you store a large video library locally)
- Ports 80, 443, 1935, and 8888 available
Plan your domain and SSL early. Set APP_URL and KEY_SERVER_BASE_URL to the final HTTPS URL before going live.
Install flow
1. System packages
Update the system and install base dependencies:
sudo apt update && sudo apt upgrade -y
sudo apt install -y \
nginx \
mysql-server \
redis-server \
supervisor \
git \
curl \
wget \
unzip \
zip \
build-essential \
ffmpeg \
software-properties-commonInstall PHP 8.2 and extensions:
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y \
php8.2-fpm \
php8.2-cli \
php8.2-mysql \
php8.2-redis \
php8.2-mbstring \
php8.2-xml \
php8.2-curl \
php8.2-zip \
php8.2-gd \
php8.2-intl \
php8.2-bcmath \
php8.2-opcache \
php8.2-pcntlInstall Node.js 20 for the frontend build:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejsInstall Composer:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer2. PHP upload limits
Video uploads need large PHP limits. Create /etc/php/8.2/fpm/conf.d/99-nixstream.ini:
upload_max_filesize = 10G
post_max_size = 10G
max_execution_time = 0
memory_limit = 2GApply the same file to the CLI config if you run artisan commands that process large files:
sudo cp /etc/php/8.2/fpm/conf.d/99-nixstream.ini /etc/php/8.2/cli/conf.d/99-nixstream.ini
sudo systemctl restart php8.2-fpm3. Database and Redis
Create a MySQL database and user:
sudo mysql -e "CREATE DATABASE nixstream CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'nixstream_user'@'localhost' IDENTIFIED BY 'your_strong_password';"
sudo mysql -e "GRANT ALL PRIVILEGES ON nixstream.* TO 'nixstream_user'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"Verify Redis is running:
sudo systemctl enable redis-server
sudo systemctl start redis-server
redis-cli pingYou should see PONG.
4. Media binaries
NixStream relies on several binaries for transcoding and live streaming.
FFmpeg
Verify FFmpeg is installed:
ffmpeg -version
ffprobe -versionShaka Packager
Download the packager binary:
sudo wget -q https://github.com/google/shaka-packager/releases/latest/download/packager-linux-x64 \
-O /usr/local/bin/packager
sudo chmod +x /usr/local/bin/packagerBento4 and NixStream CLIs
The project ships bin/media-vod, bin/media-live, and Bento4 tools in core/bin/. Make them executable:
cd /var/www/nixstream/core
chmod +x bin/media-vod bin/media-live bin/manifest
sudo cp bin/bento4/bin/* /usr/local/bin/Whisper.cpp (transcription)
Build whisper.cpp and download a model:
cd /opt
sudo git clone --depth 1 --branch v1.7.5 --recurse-submodules https://github.com/ggml-org/whisper.cpp.git
cd whisper.cpp
sudo cmake -B build -DCMAKE_BUILD_TYPE=Release
sudo cmake --build build --config Release --target whisper-cli -j"$(nproc)"
sudo bash ./models/download-ggml-model.sh baseNote the paths for your .env:
WHISPER_CLI_PATH=/opt/whisper.cpp/build/bin/whisper-cli
WHISPER_MODELS_PATH=/opt/whisper.cpp/models5. Application install
Clone or copy the project to /var/www/nixstream/core (adjust path as needed):
cd /var/www/nixstream/core
cp .env.example .envEdit .env with your database, Redis, and URL settings:
APP_NAME="NixStream"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
FRONTEND_URL=https://your-domain.com
KEY_SERVER_BASE_URL=https://your-domain.com
DB_HOST=127.0.0.1
DB_DATABASE=nixstream
DB_USERNAME=nixstream_user
DB_PASSWORD=your_strong_password
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1
FFMPEG_BINARIES=/usr/bin/ffmpeg
FFPROBE_BINARIES=/usr/bin/ffprobe
SHAKA_PACKAGER_BINARIES=/usr/local/bin/packager
MEDIA_SERVER_HOST=http://127.0.0.1:8888
MEDIA_SERVER_INGEST_IP=your-server-public-ipInstall dependencies and build:
composer install --no-dev --optimize-autoloader
npm install
npm run build
php artisan key:generate
php artisan migrate --seed
php artisan storage:linkSet correct ownership:
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cacheDefault admin after seeding:
- Email:
ADMIN_EMAILfrom.env(defaults to[email protected]) - Password: one-time temp password printed by
./install.shor./scripts/create-admin.sh(not stored in.env)
Change the password after first login.
6. Nginx configuration
Create /etc/nginx/sites-available/nixstream:
server {
listen 80;
server_name your-domain.com;
root /var/www/nixstream/nixstream/public;
index index.php index.html;
client_max_body_size 10G;
client_body_timeout 300s;
client_header_timeout 300s;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
location /storage/ {
alias /var/www/nixstream/nixstream/storage/app/public/;
expires 1y;
add_header Cache-Control "public";
}
location /api/live/ {
proxy_pass http://127.0.0.1:8888/;
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;
}
location ~ /\. {
deny all;
}
}Enable the site:
sudo ln -s /etc/nginx/sites-available/nixstream /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxAdd SSL with Certbot before going to production. See SSL setup.
7. Queue workers with Supervisor
Create /etc/supervisor/conf.d/nixstream-worker.conf:
[program:nixstream-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/nixstream/nixstream/artisan queue:work redis --queue=high,default,video-encoding --timeout=0 --memory=2048 --tries=3
directory=/var/www/nixstream/core
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/nixstream-worker.log
stdout_logfile_maxbytes=100MBStart the workers:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl statusBoth worker processes should show RUNNING.
8. Cron scheduler
Laravel needs a cron entry for scheduled tasks like recording watches:
sudo crontab -u www-data -eAdd:
* * * * * cd /var/www/nixstream/core && php artisan schedule:run >> /dev/null 2>&19. Live server
If you use live streaming, start the media-live binary. You can run it via Supervisor or systemd.
Example Supervisor config at /etc/supervisor/conf.d/nixstream-live.conf:
[program:nixstream-live]
command=/var/www/nixstream/nixstream/bin/media-live
directory=/var/www/nixstream/core
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/nixstream-live.logOpen firewall ports:
sudo ufw allow 1935/tcp
sudo ufw allow 8888/tcpSet MEDIA_SERVER_API_KEY in .env. Docker install.sh generates one for you; on manual installs, pick a random secret and use the same value in the app and live server.
Verify health:
curl http://127.0.0.1:8888/health10. Verify everything works
Run through this checklist:
- Open
https://your-domain.com/paneland log in - Upload a short test video and confirm encoding starts
- Check queue workers:
sudo supervisorctl status - Watch logs:
tail -f storage/logs/laravel.log - Confirm encoded files appear in
storage/app/public/videos/ - If using live, push a test stream from OBS and confirm it shows as active
You can also run the project verify script:
cd /var/www/nixstream/core
./scripts/verify.shCommon mistakes
Forgot php artisan storage:link
Encoded videos and thumbnails won't be web-accessible.
Queue workers not running Uploads stay in "pending" forever. Check Supervisor status and Redis connectivity.
Wrong APP_URL
Login, share links, and OAuth redirects break if the URL doesn't match what users see in the browser.
Live ingest IP not set
OBS can't connect if MEDIA_SERVER_INGEST_IP points to localhost instead of your public IP.
PHP upload limits too low
Large video uploads fail with 413 errors. Confirm both Nginx client_max_body_size and PHP post_max_size are set to 10G.
Permissions on storage/
Encoding jobs fail silently if www-data can't write to storage/.
From here: encoding profiles in the admin panel, S3 storage if media should live off-server, an API key for integrations, and monitoring for ongoing ops.