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. We will also make sure to install git and ensure that you installed all LAMP-bits properly:
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 php git 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 a2enmod rewrite sudo a2enmod proxy sudo a2enmod proxy_fcgi sudo a2enmod mime sudo a2enmod expires sudo a2enmod deflate 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' (for example) with a dedicated user for security. Log in to MySQL with mysql -u root -p and enter something like:
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;
Okay, now that the db is setup, it is time to download and configure Pixelfed.
Pixelfed is in its infancy so use the dev branch until the stable version is released:
cd /var/www git clone -b dev https://github.com/pixelfed/pixelfed.git pixelfed
Once it is downloaded, let's set up our permissions:
cd pixelfed
sudo chown www-data:www-data /var/www/pixelfed
sudo chown -R www-data:www-data ./
sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;
sudo chmod -R 775 /var/www/pixelfed/storage /var/www/pixelfed/bootstrap/cache
sudo chmod 600 /var/www/pixelfed/storage/oauth-private.key
sudo chmod 600 /var/www/pixelfed/storage/oauth-public.key
Once the permissions are setup, we can now initialize and setup Laravel, artisan, etc., and configure our virtual environment:
cd /var/www/pixelfed sudo -u www-data composer install --no-dev --optimize-autoloader
Now that dependencies are installed, let's make a copy of the example environment config with cp .env.example .env and then open it up sudo nano /var/www/pixelfed/.env and drop in some settings something like mine, but adjusted to your use-case:
APP_NAME="GNU/Linux Pics" APP_ENV=production APP_KEY=generated with artisan 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
Something like the above are the minimum settings one would want on spinup. Of course, you can add cloud storage and other stuff later if you so desire. You could also adjust it to use an off-site db and/or adjust your mailer to use sendgrid, etc. Personally, I prefer the db to be local as well as the mail server. I will eventually build a mail server on this instance and migrate the MAIL settings to using it instead, but for now, I've used an existing and working management mail server. It is now time to initialize the virtual environment using artisan:
cd /var/www/pixelfed sudo -u www-data php artisan migrate --force sudo -u www-data php artisan storage:link ls -l /var/www/pixelfed/public/storage #optionally verify storage symlink 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 sudo -u www-data php artisan instance:actor sudo -u www-data php artisan import:cities
During testing, I was having some spinning and other issues that seemed to stem from re-writes and overrides glitching a bit. For this reason, I added an override inside each vhost as an extra precaution. You also want to ensure that the /var/www/pixelfed/public sub-directory is specified and that your php handler is explicitly declared. Open up your vhost(s), and ensure these additions are within your IfModule declarations:
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>
Pixelfed has a nested .htaccess file inside public so let's ensure that Overrides are enabled globally. Let's open up sudo nano /etc/apache2/apache2.conf and change None to All in the appropriate sections (this matches the Override rule in the vhost). In the webroot block, or <Directory /var/www/>, it should look like:
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All ### NOTE "All" instead of "None" ### Require all granted </Directory>
Additionally, the Pixelfed website mentions that some users of apache might have difficulty with the default .htaccess file in public so they recommended changing it. My instance indeed had trouble finding certain locations and directories unless I changed to their recommendation. Open up sudo nano /var/www/pixelfed/public/.htaccess and enter the recommendation as follows:
<IfModule mod_rewrite.c>
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]
</IfModule>
Now that apache is fully configured, let's create out systemd unit file.
First, let's create the unit file over in sudo nano /etc/systemd/system/pixelfed.service and inside that unit file, put something similar to the following:
[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
After you create the unit file, let's reload the daemon, restart the service and check for any errors. Restart all services and check a web browser to see if the instance resolves after restarting the services.
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
At this point, most essential lifts are done. As long as your landing page resolves, which it should by this stage, you can now move on to creating an admin user and then managing the instance via the GUI web panel:
Let's create the admin user:
cd /var/www/pixelfed sudo -u www-data php artisan user:create
Follow the prompts it provides with your desired values and make sure to state yes when it asks you to make this user and admin. Navigate to your instance in a web browser and log in. If it works, then you are good to go! If not, here are some common commands I ran to test and debug things while I was setting everything up:
redis-cli ping redis-cli keys "horizon:*" journalctl -u pixelfed.service -n 50 sudo -u www-data php artisan config:show queue | grep default tail -n 100 /var/www/pixelfed/storage/logs/laravel.log | grep -i "activitypub\|federat\|outbox\|inbox\|error\|fail\|exception"
I am currently still working on getting the well-known to function correctly. I am unsure if the errors I am getting are due to the instance not yet being approved or if it is an error on my end. I will update here once confirmed:
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
For updating, something like the following is minimally required:
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
After updating, the instance will hose the permissions on the OAUTH bits, so I decided to make a fun script to reset all caches and perms as follows. I created sudo nano /usr/local/bin/refresh.sh and put the following inside it:
#!/bin/bash cd /var/www/pixelfed echo "Starting full Pixel refresh โ hold tight!" sleep 2s echo "๐ฅ Clearing all old caches..." sudo -u www-data php artisan optimize:clear sudo -u www-data php artisan config:clear sudo -u www-data php artisan cache:clear sudo -u www-data php artisan route:clear sudo -u www-data php artisan view:clear echo "๐ฅ Rebuilding fresh caches..." 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 echo "๐ฅ Re-locking down the OAuth keys ..." sudo chmod 600 /var/www/pixelfed/storage/oauth-private.key sudo chmod 600 /var/www/pixelfed/storage/oauth-public.key echo "๐ Restarting services โ bringing it all back online..." sudo systemctl restart apache2 php8.4-fpm pixelfed.service redis-server
This little script just helps me refresh everything if/when changes or adjustments need to be made. Once the instance gets approved and/or I have resolved the well-known federation and/or discovery stuff, I will post an update. Thanks all and happy hacking !!
From here forward, I post miscellaneous debugging. To fix images in DMs failing, as per github #5217, #5365, #5496, I edited the db to accept wrongly submitted null values from the bad DM code:
mysql -u root -p USE pixel; ALTER TABLE statuses MODIFY COLUMN caption TEXT NULL; EXIT;
This solves the issue and allows users to upload images inside their DMS. Here are the issues that helped me fix this:
โ oemb1905 2026/03/01 21:49