ISPConfig Hosting Control Panel on AWS User Guide
Overview
This image runs ISPConfig, the long established open source hosting control panel that lets you run a web hosting server entirely from a browser. From one interface it manages Apache web sites and their PHP settings, MariaDB databases, FTP accounts, clients and resellers, so you can host many sites and hand each customer a self-service login without editing a configuration file by hand.
The panel and its JSON-RPC remote API listen on port 8080 over TLS. Ports 80 and 443 serve the web sites you host. The image ships a focused hosting stack: Apache 2.4, PHP, MariaDB and PureFTPd, assembled and configured so the first page you reach is a working sign-in rather than an installer.
Mail and DNS management are deliberately left off. The installer is run with --no-mail --no-dns --no-firewall --no-roundcube --no-pma, and this image additionally stops and masks the BIND resolver that the installer pulls in regardless, so no open recursive resolver ships. Postfix remains installed for local delivery only (cron output and system notifications) and is bound to loopback, so there is no open SMTP relay either. MariaDB is likewise re-bound to loopback, because the ISPConfig installer opens it to all interfaces for multi-server clusters and this appliance is a single server.
On the first boot of every deployed instance a one shot service generates a fresh MariaDB root password, a fresh panel database password, a fresh panel administrator password and a fresh remote API password, all unique to that instance, writes them to /root/ispconfig-credentials.txt with mode 0600, and regenerates the panel TLS certificate for that instance's address. The panel is held offline until that rotation has completed, so there is never a window in which a shipped default could be used, and the upstream admin/admin default is rejected outright.
Hosted site content lives on a dedicated EBS volume mounted at /var/www, and the databases on a second dedicated volume at /var/lib/mysql, so both tiers can be snapshotted and resized independently of the operating system disk.
Prerequisites
Before you deploy this image you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group allowing inbound TCP 22 from your management network, inbound TCP 8080 from the networks your operators will reach the panel on, and inbound TCP 80 and 443 for the web sites you host
- The AWS CLI (version 2) installed locally if you plan to deploy from the command line
Connecting to your instance
SSH in as the default login user for your operating system variant, using the key pair you launched with.
| OS variant | Login user | Example |
|---|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i your-key.pem ubuntu@<public-ip> |
Step 1: Launch the instance from the AWS Marketplace console
- Open the product page in AWS Marketplace and choose Continue to Subscribe, then Continue to Configuration.
- Select the software version and your AWS Region, then choose Continue to Launch.
- Choose an instance type of m5.large or larger, your VPC subnet, the security group described above, and your EC2 key pair.
- Launch the instance and wait for it to reach the running state with status checks passed.
First boot initialisation takes a minute or two after the status checks pass. The panel does not answer until it has finished, which is deliberate.
Step 2: Launch the instance from the AWS CLI (alternative)
Replace the AMI id, key name, security group and subnet with your own values.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--security-group-ids <security-group-id> \
--subnet-id <subnet-id> \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ispconfig}]'
Step 3: Retrieve the generated credentials
Every credential in this image is generated on the first boot of your instance and is unique to it. They live in a root-only file.
The command below lists the credential keys without printing any secret value, so it is safe to run in a shared terminal or paste into a ticket:
sudo grep -oE '^[a-z.]+' /root/ispconfig-credentials.txt | sed 's/$/ = <value>/'
ispconfig.url = <value>
ispconfig.admin.user = <value>
ispconfig.admin.pass = <value>
ispconfig.api.user = <value>
ispconfig.api.pass = <value>
ispconfig.db.root.pass = <value>
ispconfig.db.ispconfig.user = <value>
ispconfig.db.ispconfig.pass = <value>
This next block confirms the rotation actually happened and that the file is locked down, again without revealing anything. It prints the panel URL, the length of the generated secrets, and the file's permissions and owner:
sudo grep '^ispconfig.url=' /root/ispconfig-credentials.txt
sudo awk -F= '/^ispconfig\.admin\.pass=/{print "admin password: " length($2) " characters generated for this instance"}' /root/ispconfig-credentials.txt
sudo awk -F= '/^ispconfig\.api\.pass=/{print "api password: " length($2) " characters generated for this instance"}' /root/ispconfig-credentials.txt
sudo stat -c '%n %a %U:%G' /root/ispconfig-credentials.txt
ispconfig.url=https://50.16.90.220:8080/
admin password: 28 characters generated for this instance
api password: 28 characters generated for this instance
/root/ispconfig-credentials.txt 600 root:root
To read the actual passwords when you need them, run sudo cat /root/ispconfig-credentials.txt on the instance. Copy them into your password manager and treat that file as the only record: the passwords are not recoverable from anywhere else, and cloudimg does not hold them.
Step 4: Verify the stack is running
All four units should report active. The ispconfig-firstboot unit is a one shot that stays active after completing, which is how you can tell first boot finished.
for s in mariadb.service apache2.service pure-ftpd-mysql.service ispconfig-firstboot.service; do
printf '%-30s %s\n' "$s" "$(systemctl is-active "$s")"
done
mariadb.service active
apache2.service active
pure-ftpd-mysql.service active
ispconfig-firstboot.service active
The panel answers on port 8080 with a redirect to the sign-in form:
curl -sk -o /dev/null -w 'panel login page: HTTP %{http_code}\n' --max-time 15 https://127.0.0.1:8080/
panel login page: HTTP 302
The image also ships a login round-trip checker. It proves three things at once: that the per-instance administrator password authenticates against the live panel, that the upstream admin/admin default is rejected, and that a blank password is rejected. It prints OK and exits 0 only when all three hold.
sudo python3 /usr/local/sbin/ispconfig-panel-check.py
OK
Step 5: Sign in to the panel
Open the ispconfig.url value from Step 3 in a browser, which is https://<public-ip>:8080/ for your instance. The panel serves a certificate generated for your instance on its first boot, so your browser will warn that it is not from a public authority on first use; accept the warning to proceed. Step 9 shows how to replace it with a trusted certificate.
Sign in with the ispconfig.admin.user and ispconfig.admin.pass values from the credentials file.

After signing in you land on the panel home page, which shows the available modules, upstream release news and live system metrics for the instance.

The seven modules across the top are the whole product: Client for clients and resellers, Sites for web sites, databases and FTP, Monitor for server state and logs, Tools for your own account, and System for server configuration and panel users.
Step 6: Add a client and a web site
ISPConfig separates the people from the things they own. Create a client first, then create web sites, databases and FTP accounts belonging to that client. A client gets their own panel login and their own resource limits, which is what makes the panel usable for reseller and agency hosting rather than just one server's worth of sites.
To add a client, open Client, then Add Client, and fill in at least the company or contact name, a username and a password. The Limits tab is where you cap how many web sites, databases and FTP accounts that client may create.

To add a web site, open Sites, then Website, then Add new website. Set the domain, choose the owning client, pick the PHP mode, and set the disk quota. ISPConfig writes the Apache vhost, creates the document root under /var/www and provisions the site's system user for you.

Point your domain's DNS A record at the instance's public address to serve the site publicly. Note that this image does not run a DNS server, so create that record wherever your domain is hosted.
Step 7: Provision a database
Open Sites, then Databases. Create a database user first under Database Users, then a database under Databases, assigning it to the owning client and the site it serves.

MariaDB is bound to loopback on this image, so databases are reachable from the instance itself and from the sites it hosts, but never directly from the internet. If you need remote database access, tunnel it over SSH rather than opening port 3306 in your security group.
Step 8: Automate with the JSON-RPC remote API
ISPConfig exposes a JSON-RPC API at /remote/json.php on the panel port, and this image provisions a dedicated API user for it with a password generated on first boot. The block below reads that credential into shell variables without echoing it, logs in, counts the clients the API can see, and logs out again.
API_USER=$(sudo grep '^ispconfig.api.user=' /root/ispconfig-credentials.txt | cut -d= -f2-)
API_PASS=$(sudo grep '^ispconfig.api.pass=' /root/ispconfig-credentials.txt | cut -d= -f2-)
SID=$(curl -sk --max-time 20 -H 'Content-Type: application/json' \
-d "{\"username\":\"${API_USER}\",\"password\":\"${API_PASS}\"}" \
'https://127.0.0.1:8080/remote/json.php?login' \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("response","") if d.get("code")=="ok" else "")')
if [ -n "$SID" ]; then echo "remote API login: OK (session established)"; else echo "remote API login: FAILED"; exit 1; fi
COUNT=$(curl -sk --max-time 20 -H 'Content-Type: application/json' \
-d "{\"session_id\":\"${SID}\",\"client_id\":0}" \
'https://127.0.0.1:8080/remote/json.php?client_get_all' \
| python3 -c 'import sys,json; d=json.load(sys.stdin); r=d.get("response") or []; print(len(r) if isinstance(r,list) else 0)')
echo "clients visible through the API: ${COUNT}"
curl -sk --max-time 20 -H 'Content-Type: application/json' -d "{\"session_id\":\"${SID}\"}" \
'https://127.0.0.1:8080/remote/json.php?logout' >/dev/null
echo "remote API logout: OK"
remote API login: OK (session established)
clients visible through the API: 0
remote API logout: OK
The API user ships with a scoped function whitelist covering client, web site and database management, which is what the provisioning calls above need. Widen or narrow it under System, then Remote Users. The session id is a live credential for as long as it is valid, so never log it or paste it anywhere.
Step 9: Verify the security posture
Two exposure classes matter most on a public cloud hosting appliance: an open recursive DNS resolver and an open SMTP relay. This image ships neither, and the database is not reachable off the instance. You can confirm all three yourself:
sudo ss -ltnH 'sport = :3306' | awk '{print "MariaDB listening on " $4}'
sudo ss -ltnH 'sport = :53' | awk '{print $4}' | grep -vE '^(127\.0\.0\.5[34]|127\.0\.0\.1|\[::1\])' | grep . || echo "no off-loopback DNS listener"
sudo ss -ltnH 'sport = :25' | awk '{print $4}' | grep -vE '^(127\.0\.0\.1|\[::1\])' | grep . || echo "no off-loopback SMTP listener"
MariaDB listening on 127.0.0.1:3306
no off-loopback DNS listener
no off-loopback SMTP listener
The upstream admin/admin default is refused. The block below asserts that: a successful sign-in returns a 302 redirect to the dashboard, so anything else means the credentials were refused, and the block exits non-zero if the default is ever accepted.
CODE=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 20 \
-d 's_mod=login' -d 's_pg=index' -d 'username=admin' -d 'password=admin' \
https://127.0.0.1:8080/login/index.php)
if [ "$CODE" = "302" ]; then echo "UNEXPECTED: the upstream default was accepted"; exit 1; fi
echo "upstream default admin/admin refused (HTTP ${CODE}, not a 302 redirect) - correct"
upstream default admin/admin refused (HTTP 200, not a 302 redirect) - correct
The panel certificate is generated for your instance on its first boot, not baked into the image, so no two instances share a key:
sudo openssl x509 -in /usr/local/ispconfig/interface/ssl/ispserver.crt -noout -subject -dates
subject=CN = 50.16.90.220, O = cloudimg
notBefore=Jul 26 03:17:35 2026 GMT
notAfter=Jul 23 03:17:35 2036 GMT
Apache also binds port 8081 for ISPConfig's built-in apps vhost. It is intentionally left out of the recommended security group, so it is not reachable from outside the instance. Under the recommended security group the externally reachable set is exactly 22, 80, 443 and 8080.
Step 10: Confirm the dedicated data volumes
Hosted site content and the databases each live on their own EBS volume, mounted by filesystem UUID so the layout survives reboots and re-launches:
df -h --output=source,size,avail,target /var/www /var/lib/mysql
Filesystem Size Avail Mounted on
/dev/nvme1n1 40G 38G /var/www
/dev/nvme2n1 30G 28G /var/lib/mysql
To grow either tier, modify the EBS volume in the AWS console and then extend the filesystem on the instance with sudo resize2fs against that device. Because the tiers are separate volumes you can snapshot the databases on a different schedule from the site content.
Step 11: Put a trusted certificate on the panel
The per-instance certificate is self-signed, which is fine for a panel only your operators reach but produces a browser warning. To replace it with a publicly trusted certificate, point a DNS name at the instance, open TCP 80 so the ACME HTTP-01 challenge can complete, and issue a certificate for that name:
sudo apt-get install -y certbot
sudo certbot certonly --standalone -d panel.<your-domain> --agree-tos -m admin@<your-domain> --non-interactive
sudo cp /etc/letsencrypt/live/panel.<your-domain>/fullchain.pem /usr/local/ispconfig/interface/ssl/ispserver.crt
sudo cp /etc/letsencrypt/live/panel.<your-domain>/privkey.pem /usr/local/ispconfig/interface/ssl/ispserver.key
sudo cat /usr/local/ispconfig/interface/ssl/ispserver.key /usr/local/ispconfig/interface/ssl/ispserver.crt \
| sudo tee /usr/local/ispconfig/interface/ssl/ispserver.pem >/dev/null
sudo chmod 600 /usr/local/ispconfig/interface/ssl/ispserver.key /usr/local/ispconfig/interface/ssl/ispserver.pem
sudo systemctl restart apache2
Sites you host through the panel get their own certificates independently: tick Let's Encrypt SSL on the site's SSL tab in the Sites module once the site's DNS resolves to the instance.
Step 12: Enable FTP, mail or DNS if you need them
FTP is installed and running. PureFTPd listens on port 21 with a passive port range of 40110 to 40210. Those ports are deliberately not in the recommended security group, so add inbound TCP 21 and TCP 40110-40210 to it when you want FTP, then create accounts under Sites, then FTP-Accounts. Prefer SFTP over the SSH port where you can, since plain FTP sends credentials in clear text.
Mail and DNS are not managed by this image. Enabling them means installing the service packages, re-running the ISPConfig installer so it writes the module configuration, unmasking the resolver this image masks, and opening the relevant ports. Treat that as a considered change rather than a default: an internet-facing mail server needs reverse DNS, SPF, DKIM and relay restrictions to be useful and safe, and an internet-facing name server needs recursion disabled for the world. Installing an IMAP server, for example, immediately opens ports 143 and 993 on every interface.
The outline of that change, for reference rather than to paste as-is:
sudo apt-get install -y postfix dovecot-imapd bind9
sudo systemctl unmask named
sudo /usr/local/ispconfig/server/scripts/update_from_dist.sh # re-run, enabling the mail/DNS modules
If you take that route, size the instance up, review the ISPConfig documentation for the mail and DNS modules, and restrict the new ports in your security group before exposing either to the internet.
Day-two operations
Backups. The panel's own backup settings live on each site's Backup tab. At the infrastructure level, snapshot the two EBS volumes; because the databases and the site content are separate volumes, you can keep a tighter schedule on the database tier.
Operating system updates. Unattended security upgrades are enabled in the image. Apply the rest on your own schedule and reboot into new kernels during a maintenance window.
Panel updates. ISPConfig ships its own updater at /usr/local/ispconfig/server/scripts/update_from_dist.sh. Snapshot both volumes before running it.
Monitoring. The Monitor module reports services, disk usage, logs and mail queue state for the instance from inside the panel.
Losing the credentials file. If /root/ispconfig-credentials.txt is deleted, the panel administrator password cannot be recovered from it. Reset it by updating the sys_user row in the dbispconfig database directly, using the MariaDB root password from your password manager.
Support
cloudimg provides 24/7 technical support for this image by email at support@cloudimg.co.uk, covering deployment, panel access, hosting sites, HTTPS, databases, FTP, the remote API, performance tuning and troubleshooting.
ISPConfig itself is free and open source software published under the BSD 3-Clause license. There is no licence fee and no key to enter; the cloudimg charge covers packaging, security patching, image maintenance and support. Upstream project documentation is at docs.ispconfig.org.