Coppermine Photo Gallery 1.6.31
Overview
Coppermine Photo Gallery is a free, open-source, multi-user web photo gallery (GNU GPL v3 or later) written in PHP. It organizes photos into categories and albums and makes thumbnails and intermediate sizes as you upload. It also reads EXIF data and supports comments, ratings, favorites, keyword search and per-user galleries.
This guide covers launching and verifying Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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.
What you get:
- Coppermine Photo Gallery 1.6.31, the upstream
v1.6.31release (commitd74c1d82), verified by checksum. The web installer, the self-updaters and two helper pages that fetch from the internet are removed (see Security posture). - PHP 8.3 (Ubuntu, with the PHP2Shell fixes) in its own php-fpm pool. Thumbnails are made with PHP's GD library, with no ImageMagick, and 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, and port 80 redirecting to HTTPS
- A per-instance administrator password created on first boot, with 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. 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. - Removed pages answer 404. This image removes
install.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 initializes a fresh data directory on your instance and Coppermine's own schema is loaded. The administrator is created with a 24-character password generated on your instance and stored with Coppermine's own PBKDF2 hashing.
- Keys and database access. Coppermine's form-token key (
site_token) and the TLS key are 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. - 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, and documents, video and audio are off. Anonymous visitors can view but not upload, comment, rate or send e-cards. 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, such as sign out, rating, favorites, 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. nginx also refuses any form submission sent from another site. Coppermine's own form tokens stay in force, and its 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, and 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) and pins nginx to it. It sets Coppermine's site address to
https://<that name>/, which Coppermine uses for every absolute link and email, such as password resets and 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. systemd
IPAddressDenyblocks nginx, PHP, MariaDB and the main first-boot unit from the instance metadata service (169.254.169.254, fd00:ec2::254). Only the small unit that looks up the host name at first boot may use it. Coppermine can fetch remote addresses, for its bridge manager, spam check and help pages. 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, with 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.
- Universe package. One package comes from Ubuntu's universe component (
php8.3-fpm), and 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 for the database.
Connecting to your instance
Connect over SSH as the user for your version:
Version Operating system SSH user
Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace. Choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- When 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 and your key pair.
m5.largeis recommended, and the software charge is $0.04 per vCPU per hour. - For the 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 and leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch. The image brings its 30 GiB data volume with it, and you can make it larger at launch.
Option B: AWS CLI
Run the commands below on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace 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, because 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.
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, which is the instance's public DNS name when it has one. It then creates the TLS certificate, initializes MariaDB, loads Coppermine's database and creates the administrator.
Finally, it writes /root/coppermine-credentials.txt (mode 0600, root). The commands below list the entries in that file without showing the password:
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.phpCOPPERMINE_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.phpValue 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, and there is no database password.
Confirm the gallery is running
Run the commands below to check four things. Both first-boot units have finished, and MariaDB, PHP and nginx are active. Only sshd and nginx listen beyond loopback, because 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 -1active 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/coppermineProve the login and the web defaults
The commands below check six things. The gallery answers over HTTPS, and 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. 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 it 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-roundtripgallery -> 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 sends it. 000 means the server closed the connection without an answer.
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 created by SecureAnyCloud. 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, including the gallery name, languages and themes, thumbnail sizes, registration, comments and email. Set your gallery name and the administrator's email address there first:
Add albums and photos
- Categories and Albums in the admin menu create the structure. A category holds albums, and 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 the volume bigger, increase the EBS volume's size in the EC2 console, then grow the file system in place:
sudo resize2fs "$(findmnt -no SOURCE /var/lib/coppermine)"These optional features are off on this image. To turn them on:
- Self-registration: go to Config > Registration > Allow new user registrations. Registration emails need outgoing mail, so set your SMTP server under Config > Email settings first.
- Documents, video and audio uploads: go to Config > File settings and set the allowed document, movie and audio types. Only images are allowed by default.
- Anonymous e-cards, comments and ratings: go to Groups > Anonymous.
Check the security controls
The commands below check five things. nginx, PHP, MariaDB and the main first-boot unit carry IPAddressDeny for the metadata addresses, and 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. SSH runs with the SecureAnyCloud 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 noThe fingerprint and addresses are different on every instance.
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. The command creates a new self-signed certificate for the name and pins nginx and the cross-site protection to it. 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.comRun 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 organization's CA. Copy the certificate chain and key over /etc/nginx/ssl/coppermine.crt and /etc/nginx/ssl/coppermine.key (key mode 0600, root), then 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. Alternatively, follow Coppermine's upgrade documentation, which uses
update.php. Restore that file 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 and runsudo chown -R root:rooton it. Then install it from Plugin Manager. - Backups: the database is MariaDB's
copperminedatabase, andsudo mariadb-dump coppermine > coppermine.sqluses socket authentication. The photos are in/var/lib/coppermine/albums. An EBS snapshot of the data volume is a complete backup. - Logs: web requests are in
/var/log/nginx/. Usesudo journalctl -u php8.3-fpmandsudo journalctl -u mariadbfor PHP and the database, andsudo 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: this is the cross-site protection. Open the gallery first and use its own menu.
- "You have been banned": Coppermine bans an address for a while after five wrong passwords. Wait, or run
sudo mariadb coppermine -e "DELETE FROM cpg16x_banned WHERE brute_force > 0"as root. - "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.
SecureAnyCloud'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 license 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.
SecureAnyCloud is not affiliated with, endorsed by or sponsored by the Coppermine Photo Gallery project or its developers.
Support
SecureAnyCloud provides 24/7 technical support for this image by email and live chat. We can help with deployment, first login, HTTPS and DNS setup, upgrades and troubleshooting. Contact sales@secureanycloud.com.
Deploy on AWS
Launch Coppermine Photo Gallery 1.6.31 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com