Zp
Applications AWS

ZenTao Project Management 22.7 on AWS User Guide

| Product: ZenTao Project Management 22.7 on Ubuntu 24.04 on AWS

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 as zentao (unix_socket authentication). 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 Secure and HttpOnly.
  • Only ZenTao's two entry points run PHP. index.php (the application) and api.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 zentao user 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 ZenTao sign in page with the Account and Password fields, the language menu, the Keep me logged in box, the Forgot Password link and the Log in button

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:

The ZenTao dashboard with the main menu on the left (Program, Product, Project, Execution, Test, CI and CD, Agent, Report, Kanban, Document, Company and Admin), the counters for reviews, tasks, bugs, stories, cases and features, the Guides panel and the Recents list

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:

The ZenTao Create Product form with the Program, Product Line, Product Name, Product Type, Product Owner, Reviewer and Description fields and the Save button

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:

The ZenTao Create Project form for a Scrum project with the Parent Program, Type, Name, Enable Execution, Manager, Planned Duration, Work Days, Link Products, Link Plan and Description fields

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):

The ZenTao Admin page with the System, Feature Options, Users, Features, Notifications, Add-ons, Custom Development, Data import and ZenTao Community tiles, the Popular Add-ons row and the ZenTao News and Available Upgrade column

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:

The ZenTao Add User form with the User Type, Department, Account, Password, Confirm Password, Interface Type, Name, Join Date, Role, Permission Group, Email and Gender fields

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:

The ZenTao Email settings page under Admin, Notifications, with the Select outgoing mail method prompt and the SMTP button

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:

A plain Forbidden page saying You don't have permission to access this resource, shown for ZenTao's import address

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 git and svn commands, and PHP on this image cannot run programs (and svn is 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):

The ZenTao Backup page under Admin, System, with the Start Backup button and a backup of the database, the attachments and the code, each marked Success

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::editComment does 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.