ZenTao Project Management 22.7 on AWS User Guide
Overview
This guide covers launching and using the ZenTao Project Management 22.7 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg help desk, IT service and project management listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
ZenTao is an open source project management and application lifecycle management (ALM) suite. It covers programs and products, requirements (stories), product plans and releases, Scrum or Waterfall projects with sprints and executions, tasks and effort, bugs, test cases, test runs and reports, documents and Kanban boards, with departments, users and permission groups.
What the cloudimg image adds:
- No installer and no default login. Upstream ZenTao is set up in a web installer that creates the first administrator. On this image setup is already complete when the web server first starts: first boot creates the database and sets the administrator,
admin, to a password unique to your instance. The installer and upgrader are not reachable. There are no other users. - Nothing baked into the image. The image ships an empty MariaDB data directory and no ZenTao settings file, so every instance creates its own database, settings and administrator password on first boot, and ZenTao creates its own serial number on first use.
- No database password at all. ZenTao's PHP runs as its own system user,
zentao, and MariaDB accepts that user only over the local socket from processes running aszentao(unix_socketauthentication). MariaDB has no network listener. - HTTPS from the first boot. Apache serves ZenTao on port 443 with a certificate generated on your instance, port 80 redirects to HTTPS, requests for a host name the instance does not serve are refused, and the session cookie is
SecureandHttpOnly. - Only ZenTao's two entry points run PHP.
index.php(the application) andapi.php(the REST API) are the only PHP files that can be reached. The settings file, logs, caches, backups, sources, developer tools, the installer and the upgrader answer 403 or 404. - Read only code and safe uploads. The ZenTao code is owned by root and read only to PHP, and PHP cannot run programs. Attachments and images are served by Apache as static files with a sandboxing content security policy, never run as PHP.
- The real client address. ZenTao records the address each sign in came from and locks an account for 10 minutes after 6 wrong passwords. On this image it sees the address the connection came from, never an address a client sends in a header, and the lockout also covers ZenTao's JSON sign in (the one source change, Step 12).
- Spreadsheet import is switched off because the spreadsheet library ZenTao ships has known flaws that ZenTao has not fixed (Step 8). Export works.
- Metadata and private network access blocked. Apache, PHP-FPM, MariaDB and first boot are blocked from the instance metadata service, and the
zentaouser cannot connect to addresses inside your VPC, loopback services or IPv6 unless you allow a host (an LDAP or SMTP server, Step 7). A webhook or mail server address pointed at the metadata service or an internal host gets nothing back, and the AMI requires IMDSv2. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: ZenTao 22.7 open source edition (the official release from GitHub, checksum and commit verified), Apache 2.4, PHP 8.3 FPM, MariaDB 10.11, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH) from your admin network, and TCP 443 (ZenTao) and TCP 80 (redirects to HTTPS) from the networks your team works from. Your projects, bugs and documents are often confidential, so keep 80 and 443 as narrow as you can.
- An SMTP server or relay for ZenTao's e-mail notifications (Amazon SES works), if you want them.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick ZenTao Project Management 22.7 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or larger for many users), your key pair and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=zentao}]'
First boot builds the database and takes about two minutes after the instance is running.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| ZenTao Project Management 22.7 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot creates the database, the settings file and the administrator password and mints the certificate; Apache starts only after it has finished. zentao-egress-guard loads the outbound rules for the zentao user at every boot:
systemctl is-active apache2 php8.3-fpm mariadb zentao-egress-guard zentao-firstboot
sudo test -f /var/lib/cloudimg/zentao-firstboot.done && echo FIRSTBOOT_DONE
All five units are active and the first boot marker is present:
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and Apache listen on network ports. MariaDB has no network listener at all and PHP-FPM uses a local socket:
sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {split($6, p, "\""); print $4, p[2]}' | sort
*:443 apache2
*:80 apache2
0.0.0.0:22 sshd
[::]:22 sshd
Step 4: Get your URL and password
sudo cat /root/zentao-credentials.txt
The file is readable by root only:
# ZenTao Project Management 22.7 - generated on first boot by zentao-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.
ZENTAO_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
ZENTAO_ADMIN_USER=admin
ZENTAO_ADMIN_PASSWORD=<ZENTAO_ADMIN_PASSWORD>
#
# Open ZENTAO_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to
# https) and sign in with the account and password above. The installer has already run and is not on this image.
# Spreadsheet IMPORT is disabled on this image (vendored phpspreadsheet 1.8.2 XXE, no fixed ZenTao release; see the
# guide). Export works.
# MariaDB: unix socket only (no TCP listener, no database password: ZenTao runs as the OS user zentao);
# sudo mysql zentao (root signs in over the socket)
# Change the password: sudo zentao-set-password
# Change the name it serves as: sudo zentao-set-hostname <your-dns-name>
# Hosts in this VPC ZenTao may reach (LDAP, SMTP): /etc/cloudimg/zentao-egress-allow.conf
# User guide: https://www.cloudimg.co.uk/guides/zentao-project-management-22-7-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from the EC2 instance metadata, and the certificate also names the public and private IP addresses. Apache answers only to the names in the certificate. The name is fixed at first boot, so if you stop and start the instance without an Elastic IP and its public address changes, run sudo zentao-set-hostname with the new name (Step 9), or better, give the instance an Elastic IP or your own DNS name.
To check the sign in end to end from the instance itself (HTTPS redirect, refused paths, the spreadsheet import refused, the administrator password through ZenTao's real sign in, and a wrong password refused):
sudo zentao-cred-roundtrip
OK https 301 + no installer/upgrader/my.php/sources + spreadsheet import 403 + administrator sign-in + wrong password refused
Step 5: Sign in
Open the ZENTAO_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own, Step 9). Sign in with the account admin and the password from Step 4:

The first time you sign in, ZenTao may show a short tour; close it with Got it. A new ZenTao has no products or projects yet, so the dashboard shows zero counts, the guides for each role and your own recent activity:

Make the account yours. Open the avatar menu (top right), Profile, then Edit Profile to set your name and e-mail address (password resets and notifications go there), and Change Password to choose your own password. Then delete ZENTAO_ADMIN_PASSWORD from /root/zentao-credentials.txt so it does not hold an old password.
If you lose the administrator password, set a new one from the instance. Without an option it generates a random password and writes it to the credentials file; with --stdin it reads the password you type (12 to 72 letters and digits). Either way it clears the account's lockout and signs every session out:
sudo zentao-set-password
sudo zentao-set-password --stdin
Step 6: Products, projects and people
ZenTao organises work as products (what you build: stories, plans, releases and bugs) and projects (how you build it: sprints or stages, tasks, team and effort). Product, Create Product names a product and its owner:

Project, Create Project starts a project in the model you choose (Scrum, Waterfall or another), links it to products and sets the manager and dates:

Admin holds the system settings, feature options, users and permissions, notifications and add-ons. Its right hand column and the Popular Add-ons row are news, patches and paid editions fetched from zentao.net (Step 7):

Admin, Users, Add User creates an account for each person, with a department, a role and a permission group that decides what they can see and change:

Step 7: Mail, LDAP and outbound connections
E-mail. Mail is off until you configure it. Open Admin, Notifications, Email, click SMTP and enter your server, port, encryption and account, then send the test message. Amazon SES works: use its SMTP endpoint for your Region, port 587 with TLS and an SES SMTP user:

Outbound connections from ZenTao. The zentao user (PHP-FPM) can reach public internet addresses, such as Amazon SES, a public SMTP relay or a webhook service, but not the instance metadata service, loopback services, IPv6 or any address inside your VPC. To let ZenTao reach an internal host, such as an SMTP relay or LDAP server in your VPC, add one IPv4 address or CIDR per line to the allow list, then reload the rules and restart PHP-FPM:
echo '<private-ip>' | sudo tee -a /etc/cloudimg/zentao-egress-allow.conf
sudo systemctl restart zentao-egress-guard php8.3-fpm
Metadata, link local, loopback and multicast addresses are never allowed; an entry for one of them stops the rules and PHP-FPM from starting until you remove it, so a mistake fails closed. You can see the rules in force with sudo nft list table inet cloudimg_zentao_egress.
ZenTao's own internet requests. The dashboard's Zentao Dynamic panel and the admin pages show news and add-ons from zentao.net: the server fetches the list from api.zentao.net and your browser loads the pictures from static.zentao.net. No project data is sent. If the instance has no route to the internet the panels stay empty.
Step 8: What is switched off and why
Spreadsheet import is disabled. ZenTao 22.7 bundles PhpSpreadsheet 1.8.2 to read uploaded Excel files, and that version's XLSX reader has published XML external entity (XXE) flaws (GHSA-6hwr-6v2f-3m88, GHSA-7cc9-j4mv-vcjp, GHSA-jw4x-v69f-hh5w, GHSA-ghg6-32f9-2jp7 and GHSA-3x94-m842-937c): a crafted spreadsheet could make the server read local files or contact other hosts. No ZenTao release ships a fixed version, so cloudimg refuses the import itself, twice: Apache answers 403 to any request for ZenTao's import (in the address, the API, any method, any spelling or encoding), and PHP refuses it again before ZenTao runs. Everything else works, including Export (CSV, XML and HTML) and creating stories, tasks, bugs and test cases one at a time or with ZenTao's batch create forms. If you try to import, the browser shows this:

You can check it from the instance:
H=$(sudo grep '^ZENTAO_URL=' /root/zentao-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
for p in '/index.php?m=transfer&f=import&module=story' '/index.php?f=Import&m=Transfer' '/transfer-import-story.html' '/api.php/v2/transfers/import'; do
curl -sk -o /dev/null -w "%{http_code} $p\n" --resolve "$H:443:127.0.0.1" "https://$H$p"
done
403 /index.php?m=transfer&f=import&module=story
403 /index.php?f=Import&m=Transfer
403 /transfer-import-story.html
403 /api.php/v2/transfers/import
Other things that are not available on this image:
- Code repositories. ZenTao's repository browser (CI&CD, Code) runs
gitandsvncommands, and PHP on this image cannot run programs (andsvnis not installed), so linking a repository does not work. Track commits by mentioning story, task and bug IDs in your commit messages and link them by hand, or use your Git host's own integrations. - Add-ons. Admin, Add-ons asks you to create a file on the server to confirm you are the administrator, then installs plugins by writing into the ZenTao code. The code is read only to PHP, so plugins cannot be installed. The Upgrade link at the bottom right of every page advertises ZenTao's paid editions, which are not part of this image.
- The scheduler. ZenTao's scheduled tasks (Admin, System, Cron Job: sending queued mail and webhooks, deleting old logs, updating the dashboard figures) are run by ZenTao from the browser while someone is signed in, not by an operating system timer; mail and webhooks therefore go out while your team is using ZenTao. There is no crontab on the image.
- AI features. The Agent menu is ZenTao's AI assistant. It needs a language model service you configure yourself and sends the content you ask it about to that service; nothing is configured on a new instance.
Step 9: Your own DNS name and certificate
Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name ZenTao is served as. This mints a new self signed certificate for the name, points Apache at it, updates the URL in /root/zentao-credentials.txt, and keeps the name across reboots:
sudo zentao-set-hostname <your-domain>
To use a certificate from a public or internal certificate authority instead (for example one issued by Let's Encrypt for <your-domain>), copy it over the generated pair and reload Apache (running zentao-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/apache2/ssl/zentao.crt
sudo install -m 0600 <your-domain>.key /etc/apache2/ssl/zentao.key
sudo apache2ctl configtest && sudo systemctl reload apache2
Step 10: Security checks
Apache serves only the application. The installer, the upgrader, the settings file, the developer tools, a PHP file under the uploads and the sources answer 403 or 404, plain HTTP redirects to HTTPS, and a request for a name the instance does not serve is refused:
H=$(sudo grep '^ZENTAO_URL=' /root/zentao-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
for p in / '/index.php?m=user&f=login' /install.php /upgrade.php /config/my.php /dev.php /checktable.php /data/x.php /.htaccess /README.md; do
curl -sk -o /dev/null -w "%{http_code} $p\n" --resolve "$H:443:127.0.0.1" "https://$H$p"
done
curl -s -o /dev/null -w 'http:// -> %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'unknown host name -> %{http_code}\n' --resolve "other.example:443:127.0.0.1" "https://other.example/" || true
302 /
200 /index.php?m=user&f=login
403 /install.php
403 /upgrade.php
403 /config/my.php
403 /dev.php
403 /checktable.php
403 /data/x.php
403 /.htaccess
403 /README.md
http:// -> 301
unknown host name -> 403
ZenTao's database account has no password; MariaDB accepts it only over the local socket from processes running as zentao, and MariaDB does not listen on the network:
sudo mysql -N -B -e "SELECT CONCAT(User, '@', Host, ' authenticates by ', JSON_VALUE(Priv, '$.plugin'), ', password set: ', IF(IFNULL(JSON_VALUE(Priv, '$.authentication_string'), '') = '', 'no', 'yes')) FROM mysql.global_priv WHERE User = 'zentao'; SELECT CONCAT('skip_networking: ', @@skip_networking);"
zentao@localhost authenticates by unix_socket, password set: no
skip_networking: ON
The certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^ZENTAO_URL=' /root/zentao-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
None of the services can reach the instance metadata service, so a webhook or mail server address pointed at it gets nothing back:
systemctl show apache2 php8.3-fpm mariadb zentao-firstboot -p Id -p IPAddressDeny
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
Id=apache2.service
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
Id=php8.3-fpm.service
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
Id=mariadb.service
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
Id=zentao-firstboot.service
Step 11: Backup, storage and maintenance
Everything you enter is in the MariaDB database zentao (/var/lib/mysql); attachments, sessions, caches, logs and ZenTao's own backups are under /var/lib/zentao, and the settings file is /etc/zentao/my.php. All three are on the root volume, so the simplest backup is an EBS snapshot of it.
ZenTao also makes its own backups: Admin, System, Backup, Start Backup saves a database dump, the attachments and a copy of the ZenTao code (about 180 MB) to /var/lib/zentao/tmp/backup (shown in ZenTao as /var/www/zentao/tmp/backup), and keeps the last 14 days. They stay on the instance, so copy them off it (to Amazon S3, for example):

sudo ls -l /var/lib/zentao/tmp/backup
For a database copy of your own while ZenTao is running:
sudo mariadb-dump --single-transaction --routines zentao | gzip > <backup-dir>/zentao-$(date +%F).sql.gz
sudo tar -C / -czf <backup-dir>/zentao-files-$(date +%F).tar.gz var/lib/zentao/data etc/zentao
Backups hold every password hash, so store them as carefully as the instance.
To give attachments and the database more room, enlarge the root volume in the EC2 console (Volumes, Modify); Ubuntu grows the file system on the next boot.
The code is read only to PHP, and the upstream upgrader is not reachable. New ZenTao releases arrive as new versions of this listing, and support can help you move your data to one. Operating system security updates install automatically; run sudo apt-get update && sudo apt-get upgrade to apply everything else. Apache, PHP-FPM and MariaDB restart cleanly and ZenTao keeps all of its data.
| Component | Path |
|---|---|
| ZenTao | /var/www/zentao (owned by root, read only to PHP; Apache serves www/) |
| Settings | /etc/zentao/my.php (root, readable by the zentao group) |
| Database | MariaDB database zentao (/var/lib/mysql) |
| Attachments, sessions, caches, logs and backups | /var/lib/zentao |
| Apache site | /etc/apache2/sites-available/cloudimg-zentao.conf and /etc/apache2/cloudimg-zentao-import-deny.conf |
| Certificate and key | /etc/apache2/ssl/zentao.crt, /etc/apache2/ssl/zentao.key |
| PHP-FPM pool | /etc/php/8.3/fpm/pool.d/zentao.conf |
| Outbound allow list | /etc/cloudimg/zentao-egress-allow.conf (zentao-egress-guard.service) |
| Helpers | /usr/local/sbin/zentao-set-password, zentao-set-hostname, zentao-cred-roundtrip |
| Credentials | /root/zentao-credentials.txt (mode 0600) |
| Source and changes | /usr/share/doc/zentao/SOURCE.txt |
Step 12: Known issues and security recommendations
Known issues in ZenTao 22.7 that no ZenTao release fixes yet. Both need a signed-in account:
- CVE-2026-111346 (medium): a signed-in user can edit another user's comment (
action::editCommentdoes not check who wrote it). - CVE-2026-121514 (medium): a weakness in the check ZenTao makes when a user's permission groups change (
userModel::checkGroupChange); it needs a user who may already edit users.
Give accounts only to people you trust with your projects, keep user editing to administrators, and review the Recents history if a comment looks changed. New versions of this listing will carry ZenTao's fixes when they are released.
The one source change. Upstream ZenTao 22.7 returns its answer to a failed JSON sign in (the form the ZenTao client apps use) before it counts the failure, so the 6 attempt lockout never applied to it. cloudimg changed that one line in module/user/zen.php so the failure is counted; the change and the reason are recorded in /usr/share/doc/zentao/SOURCE.txt:
grep -A4 'Modified by cloudimg' /usr/share/doc/zentao/SOURCE.txt
Recommendations:
- Allow port 22 from your admin network only, and ports 80 and 443 only from the networks your team works from.
- Use your own DNS name and a certificate your users' browsers trust (Step 9).
- Set your own e-mail address on the administrator and change its password (Step 5), and give each person their own account in a permission group with only the access they need.
- Allow only the internal hosts ZenTao needs in
/etc/cloudimg/zentao-egress-allow.conf(Step 7). - Copy ZenTao's backups off the instance, keep the operating system patched, and snapshot the root volume regularly.
Support and licensing
ZenTao is open source software by Qingdao Nature Easy Soft Network Technology Co., Ltd. (Easysoft) and its contributors, licensed under the Z Public License 1.2 or the GNU Affero General Public License version 3; cloudimg redistributes it under the AGPL-3.0. There is no per user licence fee. The image ships the ZenTao 22.7 release from GitHub with its own bundled PHP libraries. cloudimg removed test, development and optional worker mode code that is never loaded, moved the installer and upgrader out of the web root, and changed one line (Step 12); /usr/share/doc/zentao/SOURCE.txt names the release archive, its checksum and commit, every file left out and the change, and the licence texts are in /usr/share/doc/zentao. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.