This is an old revision of the document!
pixelfed
This tutorial provides users of Debian GNU/Linux with a roadmap for installing a Pixelfed instance. These isntructions are drawn from the Pixelfed documentation. Most steps were straightforward, however, there were a few issues not covered in their documentation, namely, special permissions for some OAUTH bits, initializing of storage, and a few other things. As with most other tutorials on this Wiki, make sure you first have a hardened VPS w/ LAMP ready to go - if not, head over to Apache Survival first and set that up. Okay, here we go!
Your LAMP stack might not have all php dependencies, redis, and/or imagick. Let's make sure those are installed. Also you should be using mpm_event with php8.4-fpm - not the legacy handler.
sudo apt update sudo apt install php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip php8.4-bcmath php8.4-intl php8.4-redis php8.4-imagick php8.4-imap php8.4-ldap apache2 mariadb-server* imagick* redis* -y sudo systemctl restart php8.4-fpm
If you need help switching to mpm_event, head over to the WordPress tutorial which has an adaptable codeblock (midway down) that purges the legacy handler and switches you over to mpm_event. Once you are sure these modules are installed and that you have the correct php handler, make sure they are enabled:
sudo a2enmod ssl sudo a2enmod headers sudo a2enmod cache sudo a2enmod rewrite sudo a2enmod setenvif sudo a2enmod mpm_event sudo a2enconf php8.4-fpm sudo apache2ctl configtest sudo systemctl restart apache2 sudo systemctl restart php8.4-fpm
After that, let's make sure fpm is ready for simultaneous connections by opening up sudo nano /etc/php/8.4/fpm/pool.d/www.conf and adjusting these settings to something like the suggestions below:
pm = dynamic pm.max_children = 120 pm.start_servers = 12 pm.min_spare_servers = 6 pm.max_spare_servers = 18
Of course, adjust these to your use case and expected amount of users. The suggestions above should work well for 100-200 users with roughly 10-40 using services at the same time (or overlapping). You should also make sure that your php memory is boosted a bit as well as matching (or exceeding) the Pixelfed upload limits and caps. To do that, open sudo nano /etc/php/8.4/fpm/php.ini and enter something like:
upload_max_filesize = 5G post_max_size = 5G memory_limit = 512M max_execution_time = 600 max_input_time = 600
My settings are very aggressive as there are some other things I do on this box. You can simply match Pixelfed's settings if you prefer. Okay, it's now time for DB creation.
Create a new database named 'pixel' with a dedicated user for security. Log in to MySQL as root again.
mysql -u root -p
</code>
Inside the MySQL shell, run:
CREATE DATABASE pixel CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS 'pixel'@'localhost' IDENTIFIED BY 'strongpass'; GRANT ALL PRIVILEGES ON pixel.* TO 'pixel'@'localhost'; FLUSH PRIVILEGES; EXIT;
This sets up a UTF-8 compatible database optimized for Pixelfed's schema.
sudo rm -rf /var/www/pixelfed
Clone the latest development branch from the official repository. The 'dev' branch often includes fixes and features not yet in stable.
cd /var/www git clone -b dev https://github.com/pixelfed/pixelfed.git pixelfed
sudo chown -R www-data:www-data /var/www/pixelfed sudo chmod -R 775 /var/www/pixelfed/storage /var/www/pixelfed/bootstrap/cache
This prevents permission errors during runtime, such as failed uploads or cache writes.
cd /var/www/pixelfed sudo -u www-data composer install --no-dev --optimize-autoloader
This pulls in Laravel and other required packages without development tools for a production setup.
sudo nano /var/www/pixelfed/.env
Paste or update with the following content (replace placeholders if needed, e.g., passwords, domains, or mail settings):
APP_NAME="GNU/Linux Pics" APP_ENV=production APP_KEY= # Generated later APP_DEBUG=false APP_URL=https://gnulinux.pics APP_DOMAIN=gnulinux.pics ADMIN_DOMAIN=gnulinux.pics SESSION_DOMAIN=gnulinux.pics DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=pixel DB_USERNAME=pixel DB_PASSWORD=strongpass REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 REDIS_CLIENT=predis REDIS_SCHEME=tcp CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=redis HORIZON_PREFIX=horizon- MAIL_MAILER=smtp MAIL_HOST=mail.haacksnetworking.org MAIL_PORT=587 MAIL_USERNAME=webmaster MAIL_PASSWORD=strongpass MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=webmaster@haacksnetworking.org MAIL_FROM_NAME="GNU/Linux Pics" ACTIVITY_PUB=true AP_REMOTE_FOLLOW=true AP_INBOX=true AP_OUTBOX=true AP_SHAREDINBOX=true RELAY=true OPEN_REGISTRATION=true ENFORCE_EMAIL_VERIFICATION=true PF_MAX_USERS=1000 PF_OPTIMIZE_IMAGES=true IMAGE_QUALITY=80 MAX_PHOTO_SIZE=15000 MAX_CAPTION_LENGTH=500 MAX_ALBUM_LENGTH=4 INSTANCE_DISCOVER_PUBLIC=true PF_ENABLE_CLOUD=false FILESYSTEM_CLOUD=s3 #AWS_ACCESS_KEY_ID= #AWS_SECRET_ACCESS_KEY= #AWS_DEFAULT_REGION= #AWS_BUCKET= #AWS_URL= #AWS_ENDPOINT= #AWS_USE_PATH_STYLE_ENDPOINT=false
Save and exit.
sudo -u www-data php artisan migrate --force
sudo -u www-data php artisan storage:link
Verify the symlink:
ls -l /var/www/pixelfed/public/storage
Expected output: storage → ../storage/app/public
If images fail to display after uploads, recheck permissions:
sudo chown -R www-data:www-data /var/www/pixelfed/storage sudo chmod -R 775 /var/www/pixelfed/storage #oauth bits require stronger perms sudo chmod 600 /var/www/pixelfed/storage/oauth-private.key sudo chmod 600 /var/www/pixelfed/storage/oauth-public.key
sudo -u www-data php artisan key:generate
sudo -u www-data php artisan passport:keys --force sudo -u www-data php artisan passport:install --force
sudo -u www-data php artisan horizon:install
sudo -u www-data php artisan config:cache sudo -u www-data php artisan route:cache sudo -u www-data php artisan view:cache sudo -u www-data php artisan optimize
For the non-SSL site (HTTP redirect):
sudo nano /etc/apache2/sites-enabled/000-default.conf
Paste:
<VirtualHost *:80> ServerName gnulinux.pics RewriteEngine On RewriteCond %{SERVER_NAME} =gnulinux.pics RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] </VirtualHost>
For the SSL site:
sudo nano /etc/apache2/sites-enabled/000-default-le-ssl.conf
Paste:
<VirtualHost *:443> ServerName gnulinux.pics SSLEngine on SSLCertificateFile /etc/letsencrypt/live/gnulinux.pics/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/gnulinux.pics/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf DocumentRoot /var/www/pixelfed/public <Directory /var/www/pixelfed/public> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch \.php$> SetHandler "proxy:unix:/run/php/php8.4-fpm.sock|fcgi://localhost/" </FilesMatch> LimitRequestBody 524288000 ErrorLog ${APACHE_LOG_DIR}/gnulinux-pics_error.log CustomLog ${APACHE_LOG_DIR}/gnulinux-pics_access.log combined </VirtualHost>
Ensure AllowOverride All in main config:
sudo nano /etc/apache2/apache2.conf
In the <Directory /var/www/> block ensure:
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>
sudo nano /var/www/pixelfed/public/.htaccess
Paste:
Options +FollowSymLinks -Indexes RewriteEngine On RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L]
sudo nano /etc/systemd/system/pixelfed.service
Paste:
[Unit] Description=Pixelfed Horizon Queue Worker (Laravel Horizon) After=network.target apache2.service php8.4-fpm.service redis-server.service mariadb.service Wants=apache2.service php8.4-fpm.service redis-server.service mariadb.service [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/var/www/pixelfed ExecStart=/usr/bin/php artisan horizon Restart=on-failure RestartSec=5s StandardOutput=journal StandardError=journal NoNewPrivileges=yes PrivateTmp=true ProtectSystem=strict ProtectHome=yes ReadWritePaths=/var/www/pixelfed/storage ReadWritePaths=/var/www/pixelfed/bootstrap/cache [Install] WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload sudo systemctl enable pixelfed.service sudo systemctl restart pixelfed.service sudo systemctl status pixelfed.service journalctl -u pixelfed.service -n 50
sudo apache2ctl configtest sudo systemctl reload apache2 sudo systemctl restart apache2 php8.4-fpm pixelfed.service redis-server
sudo -u www-data php artisan user:create
Follow prompts (example values):
redis-cli ping redis-cli keys "horizon:*" sudo systemctl status redis-server sudo systemctl status pixelfed.service journalctl -u pixelfed.service -n 50
sudo -u www-data php artisan config:show queue | grep default
curl -s https://gnulinux.pics/.well-known/nodeinfo curl -s https://gnulinux.pics/api/nodeinfo/2.0 sudo -u www-data php artisan route:list | grep -i nodeinfo
curl -I https://pixelfed.social/.well-known/nodeinfo curl -I https://mastodon.social/.well-known/nodeinfo
tail -n 100 /var/www/pixelfed/storage/logs/laravel.log | grep -i "activitypub\|federat\|outbox\|inbox\|error\|fail\|exception"
cd /var/www/pixelfed git pull origin dev sudo -u www-data composer install --no-dev --optimize-autoloader sudo -u www-data php artisan migrate --force sudo -u www-data php artisan config:cache sudo -u www-data php artisan route:cache sudo systemctl restart pixelfed.service apache2 php8.4-fpm redis-server
Always check GitHub for release notes before updating.
— oemb1905 2026/03/01 17:56