curl and openssl extensions
(both are standard), and Apache .htaccess support.fsn1). Set visibility to private — visitors get
access only through short-lived presigned URLs.No bucket CORS rule is needed: uploads are proxied through the webhost
(same-origin) and gallery images load via <img> presigned GET URLs, which
browsers don't subject to CORS. Keep the bucket private.
Uploads stream through PHP one image per request, so the total gallery size is
irrelevant — but each single image must fit the host's limits. Ensure
upload_max_filesize and post_max_size are at least as large as your biggest
original (e.g. 200M for RAW files); on shared hosting set these in .user.ini
or php.ini:
upload_max_filesize = 200M
post_max_size = 200M
max_execution_time is lifted per upload request in code, but if your host caps
it at the web-server level (e.g. Apache/FPM request timeout), raise that too for
large files.
The browser uploads several images at once (uploads.concurrency, default 3),
which keeps the uplink busy while the webhost forwards earlier files to S3. Each
one occupies a PHP worker for its whole S3 round trip, so on shared hosting with
a tight per-site process limit, lower it:
'concurrency' => 2, // or 1 to restore strictly serial uploads
If uploads start failing with 503s under load, that limit is the first thing to check.
cp config/config.sample.php config/config.php
cp config/credentials.sample.php config/credentials.php
Edit config/config.php:
| Key | Meaning |
|---|---|
site.name |
Fallback site title |
site.base_url |
Public base URL, used for gallery share links |
site.timezone |
Timezone for expiry checks, e.g. Europe/Berlin |
s3.endpoint |
https://<location>.your-objectstorage.com |
s3.region |
The location, e.g. fsn1 |
s3.bucket |
Bucket name |
s3.access_key / s3.secret_key |
S3 credentials |
s3.url_ttl |
Lifetime of presigned view URLs in seconds |
uploads.thumb_size |
Longest edge of grid thumbnails (browser-generated) |
uploads.resize_quality |
JPEG quality (0.0–1.0) for galleries that cap their upload resolution |
The default admin login is admin / changeme — change it in the admin
Settings page immediately after the first login.
Upload the contents of this folder into your account's document root
(usually public_html/, htdocs/ or www/) via FTP/SFTP. The site's home
page, index.php, sits directly in the document root — there is no separate
web-root subfolder to configure.
The application internals (app/, config/, data/, docs/) live inside
the document root but are blocked from the web by the root .htaccess (plus a
deny-all .htaccess inside each of app/, config/, data/ as a fallback).
Verify after deploying — each of these must return 403 Forbidden, never their contents:
https://your-domain.com/config/config.phphttps://your-domain.com/config/credentials.phphttps://your-domain.com/data/site.jsonIf they don't, your host ignores .htaccess — move app/, config/ and
data/ above the document root and adjust the paths, or contact support.
The PHP process must be able to write to:
data/ (and data/galleries/) — flat-file contentmedia/ — hero + showreel imagesconfig/ — only for the online password changeOn typical shared hosting (suEXEC/FPM running as your user) this already
works; otherwise chmod 755 the directories (or 775/777 as a last resort).
/admin/, log in, change the password (Settings)./showreel.php, scroll behavior, and
that navigation hides when scrolling down.your-objectstorage.com, not from your domain,php -S localhost:8080 router.php
router.php reproduces the .htaccess protection for the PHP built-in server
(which does not read .htaccess); it is only used locally.
Everything except real S3 traffic works without credentials; gallery pages render presigned URLs that simply won't resolve until real keys are configured.