Coppermine Photo Gallery 1.6.31 on AWS User Guide
Overview
This guide covers launching and verifying Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other media products in the listing are described on the listing's guide hub.
Coppermine Photo Gallery is a free, open-source multi-user web photo gallery (GNU GPL v3 or later) written in PHP. It organises photos into categories and albums, makes thumbnails and intermediate sizes as you upload, reads EXIF data, and supports comments, ratings, favourites, keyword search and per-user galleries.
What is on the image:
- Coppermine Photo Gallery 1.6.31, the upstream
v1.6.31release (commitd74c1d82), verified by checksum, with the web installer, the self-updaters and two helper pages that fetch from the internet removed (see Security posture) - PHP 8.3 (Ubuntu, with the PHP2Shell fixes) in its own php-fpm pool, thumbnails made with PHP's GD library (no ImageMagick, nothing in PHP can run a shell command)
- MariaDB 11.4 LTS from the MariaDB Foundation's signed apt repository, on a unix socket only, with no database password anywhere
- nginx serving HTTPS on port 443 with a certificate created on your instance; port 80 redirects to HTTPS
- A per-instance administrator password created on first boot; there is no installer and no default account
- A dedicated 30 GiB data volume mounted at
/var/lib/coppermine(the database, your photos and their thumbnails) coppermine-set-hostnameto serve the gallery under your own DNS name,coppermine-set-passwordto change the administrator password, andcoppermine-cred-roundtrip, an end-to-end check
Default user: ubuntu · Architecture: x86_64 · Root volume: 12 GiB gp3 · Data volume: 30 GiB gp3
Security posture
- No web installer, no self-updater. Coppermine's
install.phpwould hand the gallery to the first visitor, and itsupdate.phphas a long history of trouble: in releases up to 1.6.29 a single unauthenticated request could rewrite the configuration file with code of the attacker's choosing (fixed upstream in 1.6.30), and 1.6.31 still runs part of the update process without signing in. This image removesinstall.php,update.php,upgrader.phpandupdater.php, plusversioncheck.phpandphpinfo.php; nginx answers all of them with 404. The configuration file is owned by root and PHP cannot write it. - Per-instance secrets, no database password. The image ships no database. On first boot MariaDB initialises a fresh data directory on your instance, Coppermine's own schema is loaded, and the administrator is created with a 24-character password generated on your instance and stored with Coppermine's own PBKDF2 hashing. Coppermine's form-token key (
site_token) is also generated on your instance. Coppermine connects to MariaDB as the operating-system user it runs as (MariaDB socket authentication), so there is no database password to steal, rotate or leak. The TLS key is generated on your instance too. - Fails closed. nginx starts only after first boot has finished and the certificate exists. If first boot cannot finish, nothing is served.
- Secure defaults. Self-registration is off, uploads are limited to JPEG, PNG and GIF images (documents, video and audio are off), anonymous visitors can view but not upload, comment, rate or send e-cards, and the Coppermine news feed is off.
- Uploads cannot run as code. Coppermine checks each upload's type on the server and refuses anything that is not a real image. The
albums/folder where photos are stored never runs PHP; nginx serves images there as images and anything else as a download, always withX-Content-Type-Options: nosniffand a sandboxing Content-Security-Policy. - Only Coppermine's own pages run. PHP runs only for Coppermine's top-level pages; its
include/,sql/,logs/,bridge/,lang/andtools/folders, plugin and theme code, and documentation files are never served. PHP runs as the unprivilegedcoppermineuser withopen_basedir, no URL file access and no shell-out functions. - Cross-site request protection. Coppermine performs many actions from plain links (sign out, rating, favourites, deleting, its admin tools and plugin actions). nginx refuses those pages unless the request comes from the gallery itself (the browser's
Sec-Fetch-Site,OriginorReferermust name this host), and refuses any form submission sent from another site. Coppermine's own form tokens stay in force. Coppermine's cookies are sent withSecure,HttpOnlyandSameSite=Lax. - Sign-in protection. nginx limits sign-in, password-reset and registration attempts per client address, on top of Coppermine's own ban after five wrong passwords.
- HTTPS from first boot. nginx serves TLS 1.2 and 1.3 with a self-signed certificate created on your instance (TLS 1.1 is refused). The certificate is an end-entity certificate (
CA:FALSE) for this instance's name and addresses only; compare its SHA-256 fingerprint (Step 7) rather than adding it to a trust store, or replace it with a certificate from a public CA (Step 8). - The host name comes from configuration, never from a request. First boot reads the instance's public DNS name (else its public or private IPv4 address) from EC2 instance metadata (IMDSv2), pins nginx to it and sets Coppermine's site address to
https://<that name>/, which Coppermine uses for every absolute link and e-mail (password resets, e-cards). Requests for any other host name get their connection closed. - Client address from the connection. nginx replaces
X-Forwarded-For,X-Real-IPandClient-IPwith the real peer address before PHP sees them, so Coppermine's sign-in ban and its records see the real client. - No access to instance metadata. nginx, PHP, MariaDB and the main first-boot unit are blocked from the instance metadata service (169.254.169.254, fd00:ec2::254) by systemd
IPAddressDeny; only the small unit that looks up the host name at first boot may use it. Coppermine can fetch remote addresses (its bridge manager, spam check and help pages), and at build time a fetch of the metadata service from Coppermine's PHP pool was refused while a local connection worked. - Hardened SSH and system. SSH allows keys only, no root login and no X11 forwarding. The AMI requires IMDSv2. SSH host keys and the machine ID are generated fresh on every instance. Ubuntu packages are fully patched at build time and unattended security upgrades are on. One package comes from Ubuntu's universe component (
php8.3-fpm); at build time it had no pending security updates. Attaching the instance to Ubuntu Pro adds Expanded Security Maintenance for universe packages.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- Your photos, which you upload in the browser
Recommended instance type: m5.large (2 vCPU, 8 GiB). Coppermine itself is light; the memory is for thumbnail generation of large photos and the database.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group opens SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address, and restrict 80 and 443 to the networks your visitors use.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch. The image brings its 30 GiB data volume with it; you can make it larger at launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDRs with your own:
SG=$(aws ec2 create-security-group --group-name coppermine \
--description "Coppermine Photo Gallery" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-visitor-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr <your-visitor-cidr>
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--block-device-mappings 'DeviceName=/dev/sdf,Ebs={VolumeSize=200,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=coppermine}]'
The block device mapping grows the data volume to 200 GiB; leave it out to keep 30 GiB. You do not need --metadata-options: the AMI already makes IMDSv2 required. Associate an Elastic IP if you want the address to survive a stop and start (see Step 8 for using your own DNS name).
Step 2: Connect and find your login
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot takes well under a minute after the instance starts. It sets the name the gallery is served as (the instance's public DNS name when it has one), creates the TLS certificate, initialises MariaDB, loads Coppermine's database, creates the administrator, and writes /root/coppermine-credentials.txt (mode 0600, root). These are the entries in that file (the password is not shown here):
sudo grep -E '^(COPPERMINE_URL|COPPERMINE_LOGIN_URL|COPPERMINE_ADMIN_USER)=' /root/coppermine-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/coppermine-credentials.txt
sudo stat -c '%a %U:%G %n' /root/coppermine-credentials.txt /var/www/coppermine/include/config.inc.php
COPPERMINE_URL=https://ec2-3-88-195-190.compute-1.amazonaws.com/
COPPERMINE_LOGIN_URL=https://ec2-3-88-195-190.compute-1.amazonaws.com/login.php
COPPERMINE_ADMIN_USER=admin
COPPERMINE_URL=
COPPERMINE_LOGIN_URL=
COPPERMINE_ADMIN_USER=
COPPERMINE_ADMIN_PASSWORD=
600 root:root /root/coppermine-credentials.txt
640 root:coppermine /var/www/coppermine/include/config.inc.php
| Value | What it is for |
|---|---|
COPPERMINE_URL |
The address of this instance's gallery |
COPPERMINE_LOGIN_URL |
The sign-in page |
COPPERMINE_ADMIN_USER |
Sign-in name of the administrator (admin) |
COPPERMINE_ADMIN_PASSWORD |
Administrator password, generated on this instance |
To read the password, run sudo cat /root/coppermine-credentials.txt in your own session and store it in your password manager. config.inc.php holds only the database name and user: there is no database password.
Step 3: Confirm the gallery is running
Both first-boot units have finished, MariaDB, PHP and nginx are active, and only sshd and nginx listen beyond loopback (MariaDB and PHP use unix sockets only). The data volume is mounted at /var/lib/coppermine (nginx runs one worker per vCPU, so the number of nginx entries follows your instance size):
systemctl is-active coppermine-host.service coppermine-firstboot.service mariadb.service php8.3-fpm.service nginx.service
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
grep -E '^(Coppermine|commit|sha256|licence)' /usr/share/doc/coppermine/RELEASE
php -r 'echo "PHP ", PHP_VERSION, "\n";'
mariadb --version
df -h /var/lib/coppermine | tail -1
active
active
active
active
active
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.53%lo:53 users:(("systemd-resolve"))
127.0.0.54:53 users:(("systemd-resolve"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
Coppermine Photo Gallery 1.6.31
commit d74c1d82e76d60dd037b4ba5eab2b5c110bb2b8c
sha256 ac3c8a2afd94a95fb4121f685481f25d0cae72109038669b6483443560219f33
licence GPL-3.0-or-later (LICENSE.txt sha256 589ed823e9a84c56feb95ac58e7cf384626b9cbf4fda2a907bc36e103de1bad2)
PHP 8.3.6
mariadb from 11.4.13-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
/dev/nvme1n1 30G 40K 28G 1% /var/lib/coppermine
Step 4: Prove the login and the web defaults
The gallery answers over HTTPS, plain HTTP redirects to HTTPS, the removed installer and updater pages and the configuration file answer 404, an unknown Host header gets no answer, TLS 1.1 is not offered and a cross-site request to the sign-out page is refused. The last command is the image's own round-trip check: it signs in through Coppermine's real sign-in form with this instance's administrator password and proves that a wrong password and common default passwords are refused:
H=$(sed -n 's/^set \$cpg_host "\(.*\)";$/\1/p' /etc/nginx/coppermine-host.conf)
C=(--connect-to ::127.0.0.1:)
curl -sk "${C[@]}" -o /dev/null -w 'gallery -> %{http_code} %{content_type}\n' "https://$H/"
curl -s "${C[@]}" -o /dev/null -w 'http -> %{http_code} %{redirect_url}\n' "http://$H/"
for p in install.php update.php upgrader.php include/config.inc.php; do curl -sk "${C[@]}" -o /dev/null -w "$p -> %{http_code}\n" "https://$H/$p"; done
curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
curl -sk "${C[@]}" -o /dev/null -w 'TLS 1.1 -> %{http_code}\n' --tlsv1.1 --tls-max 1.1 "https://$H/" || true
curl -sk "${C[@]}" -o /dev/null -w 'cross-site logout -> %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://$H/logout.php"
sudo coppermine-cred-roundtrip
gallery -> 200 text/html; charset=utf-8
http -> 301 https://ec2-3-88-195-190.compute-1.amazonaws.com/
install.php -> 404
update.php -> 404
upgrader.php -> 404
include/config.inc.php -> 404
unknown Host -> 000
TLS 1.1 -> 000
cross-site logout -> 403
OK https 301 + installer/updater/config 404 + admin login + wrong and default passwords refused
--connect-to ::127.0.0.1: sends each request to this instance while keeping the real host name in the request, exactly as a browser would send it. 000 means the server closed the connection without an answer.
Step 5: Sign in to Coppermine
Browse to COPPERMINE_LOGIN_URL (https://<public-dns-name>/login.php). The certificate is self-signed, so your browser warns once; compare the fingerprint from Step 7 and accept it for this host, or install your own certificate (Step 8). Sign in with COPPERMINE_ADMIN_USER and COPPERMINE_ADMIN_PASSWORD.

The gallery's front page lists your categories and albums with their thumbnails. The screenshots show a small demo album of images generated by cloudimg; your instance starts with an empty gallery:

Open an album and choose a picture to see it with its details, rating and comments:

Config in the admin menu holds every gallery setting: the gallery name, languages and themes, thumbnail sizes, registration, comments, e-mail and more. Set your gallery name and the administrator's e-mail address there first:

Step 6: Add albums and photos
- Categories and Albums in the admin menu create the structure: a category holds albums (an album can also sit outside any category).
- Upload file uploads photos into an album you choose; Coppermine makes the thumbnail and the intermediate size as each photo arrives. The image accepts photos of up to 16 MB and 4096 pixels on the longer side; change both under Config > File settings (nginx accepts uploads of up to 34 MB).
- To let other people upload, create user accounts under Users and give their group upload rights under Groups.
Everything that grows with your gallery lives on the data volume: albums/ (your photos and their thumbnails) and mysql/ (the database). To make it bigger, increase the EBS volume's size in the EC2 console and then grow the file system in place:
sudo resize2fs "$(findmnt -no SOURCE /var/lib/coppermine)"
Optional features that are off on this image and how to turn them on:
- Self-registration: Config > Registration > Allow new user registrations. Registration e-mails need outgoing mail: set your SMTP server under Config > Email settings first.
- Documents, video and audio uploads: Config > File settings > the allowed document, movie and audio types. Only images are allowed by default.
- Anonymous e-cards, comments and ratings: Groups > Anonymous.
Step 7: Check the security controls
nginx, PHP, MariaDB and the main first-boot unit carry IPAddressDeny for the metadata addresses, first boot recorded that it could not reach the metadata service from inside its own unit, the secure defaults are set in Coppermine's configuration, the certificate is an end-entity certificate for this instance, and SSH runs with the cloudimg pins. Compare the fingerprint with the one your browser shows:
systemctl show -p IPAddressDeny nginx.service php8.3-fpm.service mariadb.service coppermine-firstboot.service
sudo journalctl -u coppermine-firstboot.service -o cat | grep -o 'IMDS-REFUSED-IN-FIRSTBOOT.*' | tail -1
sudo mariadb coppermine -N -e "SELECT name, value FROM cpg16x_config WHERE name IN ('allow_user_registration','allowed_img_types','allowed_doc_types','allowed_mov_types','allowed_snd_types','display_coppermine_news','thumb_method','ecards_more_pic_target') ORDER BY name"
openssl x509 -in /etc/nginx/ssl/coppermine.crt -noout -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName
openssl x509 -in /etc/nginx/ssl/coppermine.crt -noout -fingerprint -sha256
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|x11forwarding) '
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IMDS-REFUSED-IN-FIRSTBOOT (http 000; IPAddressDeny in effect)
allow_user_registration 0
allowed_doc_types
allowed_img_types jpeg/jpg/png/gif
allowed_mov_types
allowed_snd_types
display_coppermine_news 0
ecards_more_pic_target https://ec2-3-88-195-190.compute-1.amazonaws.com/
thumb_method gd2
X509v3 Subject Alternative Name:
DNS:ec2-3-88-195-190.compute-1.amazonaws.com, IP Address:3.88.195.190, IP Address:172.31.95.118
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
sha256 Fingerprint=82:86:5F:96:5D:00:A4:25:F9:5D:AF:AF:58:C0:B7:5A:66:8D:E7:7A:82:9D:27:1D:21:1D:7D:28:55:B3:19:BA
permitrootlogin no
passwordauthentication no
x11forwarding no
The fingerprint and addresses are different on every instance.
Step 8: Serve the gallery under your own DNS name
Point a DNS record (for example photos.example.com) at the instance's public or Elastic IP address, then tell the image to serve that name. It creates a new self-signed certificate for the name, pins nginx and the cross-site protection to it, sets Coppermine's site address to https://photos.example.com/, updates the credentials file and keeps the name across reboots:
sudo coppermine-set-hostname photos.example.com
Run the same command after a stop and start without an Elastic IP: the instance's public DNS name changes, and nginx only answers the name it was given.
For a trusted certificate, obtain one for that name (for example with certbot's certonly mode using its own temporary web server, or from your organisation's CA), copy the certificate chain and key over /etc/nginx/ssl/coppermine.crt and /etc/nginx/ssl/coppermine.key (key mode 0600, root), and run sudo systemctl reload nginx.
Maintenance
- Change the administrator password:
sudo coppermine-set-passwordgenerates a new one and writes it to the credentials file (--stdinreads one you choose). You can also change it in Coppermine under Profile. - Operating system updates: unattended security upgrades are on, including MariaDB from its own repository. Reboot when
/var/run/reboot-requiredexists; MariaDB, PHP and nginx start again by themselves. - Coppermine updates: this image has no web updater. To move to a newer release, launch the newer version of this image (when the listing offers it) and move the data volume across, or follow Coppermine's upgrade documentation (which uses
update.php; restore it only for the upgrade and delete it again afterwards). - Plugins: the code folder is read-only to PHP, so the plugin manager cannot unpack uploaded plugins. To add one, unzip it into
/var/www/coppermine/plugins/as root (sudo chown -R root:rootit), then install it from Plugin Manager. - Backups: the database is MariaDB's
copperminedatabase (sudo mariadb-dump coppermine > coppermine.sqluses socket authentication) and the photos are in/var/lib/coppermine/albums. An EBS snapshot of the data volume is a complete backup. - Logs:
/var/log/nginx/for web requests,sudo journalctl -u php8.3-fpmandsudo journalctl -u mariadb,sudo journalctl -u coppermine-host -u coppermine-firstbootfor first boot. Coppermine's own logging (Config > Logging and statistics) writes to/var/lib/coppermine/logs. - What the instance contacts: Coppermine itself contacts nothing on its own on this image (its news feed and version check are off or removed, and its help pages, which load from coppermine-gallery.com, are not served). The admin menu's Documentation link opens coppermine-gallery.com in your browser. The operating system contacts the AWS Systems Manager agent endpoints, Ubuntu's update servers and the time service; disable the SSM agent if you do not use Session Manager.
Troubleshooting
- The web page does not load right after launch: first boot is still running.
sudo journalctl -u coppermine-host.service -u coppermine-firstboot.service -fshows its progress. - Browser shows "connection closed": nginx answers only the configured name. Use
COPPERMINE_URL, or runsudo coppermine-set-hostname <name>for a new name. - "403 Forbidden" when you follow a link from another site to an admin page: that is the cross-site protection; open the gallery first and use its own menu.
- "You have been banned": Coppermine bans an address after five wrong passwords for a while. Wait, or as root run
sudo mariadb coppermine -e "DELETE FROM cpg16x_banned WHERE brute_force > 0". - "Check versions", "phpinfo" or "Update database" in the admin menu show 404: those pages are removed on this image (see Security posture).
- The end-to-end check:
sudo coppermine-cred-roundtripprintsOKwhen everything works.
Licensing
Coppermine Photo Gallery is licensed under the GNU General Public License version 3 or later. It is written in PHP, so its complete source is the code on your instance in /var/www/coppermine. cloudimg's changes are: the removal of install.php, update.php, upgrader.php, updater.php, versioncheck.php, lang_check.php and phpinfo.php, the configuration file include/config.inc.php, and albums/ and logs/ moved onto the data volume; they are recorded in /usr/share/doc/coppermine/RELEASE with the checksum of the upstream release. The licence text is in /usr/share/doc/coppermine/LICENSE.txt. The unmodified source of this release is at github.com/coppermine-gallery/cpg1.6.x/tree/v1.6.31. MariaDB is licensed under the GPL v2 and PHP under the PHP License. cloudimg is not affiliated with, endorsed by or sponsored by the Coppermine Photo Gallery project or its developers.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: deployment, first login, HTTPS and DNS setup, upgrades and troubleshooting. Contact support@cloudimg.co.uk.