Skip to content

Run Picstome on your own server

In the panel: Picstome is free to install on your own server. This is the whole process, from an empty VPS to your first login.

Picstome is free and open source. You can run it on your own server and keep every feature: galleries, sharing, contracts, invoices, Stripe, camera upload and the desktop apps. There is no paid edition and no feature flag.

The only things you pay for are the server and, if you want, the installation service described at the end.

A server running Ubuntu 22.04 or 24.04, with root access. The realistic minimum for a small studio:

Resource Minimum Comfortable
CPU 2 vCPU 4 vCPU
RAM 2 GB 4 GB
Disk 40 GB 100 GB or more
Software PHP 8.5, MySQL 8 / MariaDB 10.6+, Nginx, Node 22+ same

Photo processing is the expensive part: RAW files and large videos need CPU and RAM while they are converted. If you plan to ingest thousands of photos per session, get more than the minimum.

Self-hosting means you own the server, the database and the backups. Nobody else has a copy of your photos, and nobody else can restore them if the disk fails. Read the backups section before you upload a real client gallery.

  1. Point the domain at the server. Create an A record for photos.yourdomain.com pointing to the server IP. Do this first: the HTTPS step fails without it.

  2. Install the packages.

    Terminal window
    apt update && apt upgrade -y
    apt install -y nginx mysql-server unzip git supervisor \
    php8.5-fpm php8.5-cli php8.5-mysql php8.5-mbstring php8.5-xml \
    php8.5-curl php8.5-zip php8.5-gd php8.5-intl php8.5-imagick

    Check with php -v that you are on 8.5. If your distribution does not ship it yet, add the ondrej/php PPA first:

    Terminal window
    add-apt-repository ppa:ondrej/php
    apt update
  3. Create the database.

    Terminal window
    mysql -u root -p
    CREATE DATABASE picstome CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'picstome'@'localhost' IDENTIFIED BY 'a-long-random-password';
    GRANT ALL PRIVILEGES ON picstome.* TO 'picstome'@'localhost';
    FLUSH PRIVILEGES;
  4. Get the code and the dependencies.

    Terminal window
    cd /var/www
    git clone https://github.com/picstome/picstome.git
    cd picstome
    composer install --no-dev --optimize-autoloader
    npm ci && npm run build
  5. Configure the environment.

    Terminal window
    cp .env.example .env
    php artisan key:generate

    Then edit .env and set at least:

    APP_NAME=Picstome
    APP_ENV=production
    APP_DEBUG=false
    APP_URL=https://photos.yourdomain.com
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=picstome
    DB_USERNAME=picstome
    DB_PASSWORD=a-long-random-password
  6. Create the schema and the first admin.

    Terminal window
    php artisan migrate --force
    php artisan create-admin-user
    php artisan storage:link
  7. Nginx. Create /etc/nginx/sites-available/picstome:

    server {
    listen 80;
    server_name photos.yourdomain.com;
    root /var/www/picstome/public;
    index index.php;
    client_max_body_size 5120M; # match the 5 GB per-file limit of the app
    location / {
    try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
    fastcgi_read_timeout 3600;
    }
    location ~ /\.(?!well-known).* {
    deny all;
    }
    }
    Terminal window
    ln -s /etc/nginx/sites-available/picstome /etc/nginx/sites-enabled/
    nginx -t && systemctl reload nginx
  8. HTTPS.

    Terminal window
    apt install -y certbot python3-certbot-nginx
    certbot --nginx -d photos.yourdomain.com
  9. The background worker. Uploads, RAW conversion, gallery ZIPs and emails run in a queue. Without it, photos stay in processing forever.

    Create /etc/supervisor/conf.d/picstome-worker.conf:

    [program:picstome-worker]
    command=php /var/www/picstome/artisan queue:work --sleep=3 --tries=3 --max-time=3600
    autostart=true
    autorestart=true
    user=www-data
    numprocs=2
    stdout_logfile=/var/www/picstome/storage/logs/worker.log
    stopwaitsecs=3600
    Terminal window
    supervisorctl reread && supervisorctl update
  10. The scheduler. Expiry reminders, cleanup and pruning run on Laravel’s schedule. Add it to root’s crontab with crontab -e:

    * * * * * cd /var/www/picstome && php artisan schedule:run >> /dev/null 2>&1
  11. Permissions and caches.

    Terminal window
    chown -R www-data:www-data /var/www/picstome
    chmod -R 775 storage bootstrap/cache
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache
  12. Log in. Open https://photos.yourdomain.com with the admin user you created and change the password immediately.

Two INI values cause most self-hosting support requests. Edit /etc/php/8.5/fpm/php.ini and restart the service:

upload_max_filesize = 5120M
post_max_size = 5120M
max_execution_time = 3600
memory_limit = 512M

upload_max_filesize also has to match client_max_body_size in Nginx. If one is smaller than the other, large uploads fail with a generic error.

For very large files, the queue worker is what actually processes them, so give PHP-FPM a generous max_execution_time and keep numprocs at 2 or more.

Local disk works, but S3-compatible storage (AWS S3, Cloudflare R2, Hetzner, Backblaze B2, MinIO) is a better fit for photos: cheaper per GB, no disk limits and downloads served directly by the storage provider.

Set these in .env:

FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=auto
AWS_BUCKET=picstome
AWS_ENDPOINT=https://your-provider-endpoint
AWS_USE_PATH_STYLE_ENDPOINT=false

Then run php artisan storage:link again and upload a test photo. The bucket must be private: Picstome generates short-lived links for every download.

There is no auto-update. A safe update, every time:

Terminal window
cd /var/www/picstome
php artisan down
git pull
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
php artisan queue:restart
php artisan up

php artisan queue:restart is not optional: workers keep the old code in memory until they restart.

Take a backup of the database and of storage/app before every update. Migrations are additive, but a backup is what turns a bad update into a bad afternoon.

Three things to back up, in this order:

  1. The database — mysqldump picstome > picstome.sql on a schedule.
  2. storage/app — the files not in S3. If you use S3, back up the bucket separately.
  3. .env — it holds the app key; without it, encrypted values are unreadable.
Symptom Cause
Photos stuck in processing The queue worker is not running. supervisorctl status.
500 on every page after an update Stale caches. php artisan optimize:clear.
Large uploads fail client_max_body_size or upload_max_filesize too low.
Emails not delivered Set the MAIL_* variables; the default log driver writes to storage/logs.
Gallery download never becomes ready Worker stopped or the archive disk is full.
Camera upload connects but nothing appears The FTP inbox path or the account state; check the camera dialog.

Logs live in storage/logs/laravel.log and storage/logs/worker.log.

Before you invite a client:

  • APP_ENV=production and APP_DEBUG=false.
  • The admin password is not the default one.
  • HTTPS is active and the HTTP version redirects.
  • The database is not reachable from the internet.
  • php artisan about shows no local environment.
  • Backups run and you have restored one at least once.

If all of this is not how you want to spend an afternoon, we install Picstome on your own server for €100. You keep the server, the data and the updates; we do the setup.

What the service includes:

  • a fresh Ubuntu server configured with Nginx, PHP, MySQL, HTTPS and the queue worker;
  • the app installed and migrated, with your admin user ready;
  • storage configured (local disk or your S3 bucket);
  • a test gallery uploaded and a shared link verified end to end;
  • email delivery checked with a real send;
  • a short handover with the credentials, the backup command and the update command.

What it does not include: the server itself, the domain, S3 costs or any third-party subscription. Write to [email protected] with the provider and the server size you have in mind.