ProjeQtOr 13.1.1
Overview
ProjeQtOr is an open source project management suite. One web application covers planning (projects, activities, milestones, dependencies and a Gantt chart that ProjeQtOr calculates from workload and resource availability), ticketing, agile boards, time and expense tracking, risks, issues, decisions, meetings, quality and requirements, budgets, invoices and reports. Profiles control what each user sees and changes.
This guide covers launching and using the ProjeQtOr 13.1.1 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud help desk, IT service and project management listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get:
- No web installer and no default login. Upstream asks you to finish the setup in a browser page that writes the database settings, and seeds the accounts
admin/adminandguest/guest. On this image, first boot creates the database and the settings itself, and the installer pages are not shipped. Theadminaccount gets a password unique to your instance (upstream'sadmin/adminis refused), and theguestaccount is closed and locked. - No database password at all. ProjeQtOr runs as its own system user,
projeqtor, and MariaDB accepts that user only over the local socket from processes running asprojeqtor(unix_socketauthentication). MariaDB has no network listener. The image ships with an empty database directory, so every instance builds its own database on first boot. - HTTPS from the first boot. nginx serves ProjeQtOr 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 dropped, and the session cookie is
Secure,HttpOnlyandSameSite=Lax. ProjeQtOr's "remember me" sign-in is off, and passwords must be at least 12 characters. - Only the application on the web. PHP runs only for ProjeQtOr's own pages and endpoints. The database scripts, the model classes, the plugin installer, the web self-updater, the data cloning tool, uploaded attachments and documents, logs and the configuration answer 404. Uploaded images are served as static files under a sandboxing content security policy, never as code, and attachments are downloaded only through ProjeQtOr's access checks.
- No call home. On every sign-in, ProjeQtOr's pages ask the browser to fetch a news feed from projeqtor.org and send it statistics about the instance. Every page on this image carries
Content-Security-Policy: connect-src 'self', so the browser refuses that request, and sending statistics is switched off. - The REST API and SAML sign-in are closed. ProjeQtOr's API identifies callers by the user name the web server hands it, so behind nginx it would accept any password. It answers 404 until you put it behind nginx authentication (Step 9). The bundled SAML toolkit has a published signature bypass advisory, so
/sso/answers 404 as well. - A hardened rich text editor. ProjeQtOr 13.1.1 bundles CKEditor 4.11.2, which has three high-severity advisories. The image replaces it with CKEditor 4.22.1, removes the Source button and source mode, and turns on CKEditor's content filter with an explicit list of allowed elements (Step 7).
- Hardened defaults. nginx, PHP-FPM, MariaDB, ProjeQtOr's cron and the first boot service are blocked from the instance metadata service, and the AMI requires IMDSv2. The code is owned by root and read-only to PHP. PHP is handed the configured host name and the real client address, never values a client sends. Sign-in attempts are rate limited, and cross-site requests to ProjeQtOr's endpoints are refused.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: ProjeQtOr 13.1.1 (the official release from SourceForge, checksum verified), CKEditor 4.22.1, nginx, 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 (ProjeQtOr) and TCP 80 (redirects to HTTPS) from the networks your users work from. Your project data is in ProjeQtOr, so keep ports 80 and 443 as narrow as you can.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick ProjeQtOr 13.1.1 on Ubuntu 24.04 as the software version and select your Region, then choose Launch through EC2. Choose m5.large (or larger for many users), your key pair and a security group as described in the prerequisites.
To launch with the AWS CLI instead, 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=projeqtor}]'First boot builds the database. It takes about a minute after the instance is running.
Connect to your instance
Version SSH user
ProjeQtOr 13.1.1 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot creates the database, sets the password and generates the certificate. nginx and ProjeQtOr's cron start only after it has finished:
systemctl is-active nginx php8.3-fpm mariadb projeqtor-cron projeqtor-firstboot
sudo test -f /var/lib/sac/data/projeqtor-firstboot.done && echo FIRSTBOOT_DONEAll five units are active and the first boot marker is present:
active active active active active FIRSTBOOT_DONEOnly SSH and nginx listen on network ports. MariaDB has no network listener, 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]}' | sort0.0.0.0:22 sshd 0.0.0.0:443 nginx 0.0.0.0:80 nginx [::]:22 sshd [::]:443 nginx [::]:80 nginxGet your URL and password
sudo cat /root/projeqtor-credentials.txtThe file is readable by root only:
# ProjeQtOr 13.1.1 - generated on first boot by projeqtor-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret. PROJEQTOR_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/ PROJEQTOR_ADMIN_USER=admin PROJEQTOR_ADMIN_PASSWORD=<PROJEQTOR_ADMIN_PASSWORD>
#
# Open PROJEQTOR_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to # https) and sign in with the user and password above. The seeded 'guest' account is closed and locked. # MariaDB: unix socket only (no TCP listener, no database password: ProjeQtOr runs as the OS user projeqtor); # sudo mysql projeqtor (root signs in over the socket) # Cron:
systemctl status projeqtor-cron PHP CLI as ProjeQtOr:
sudo projeqtor-php <script.php>
# Change the password:
sudo projeqtor-set-password
# Change the name it serves as:
sudo projeqtor-set-hostname <your-dns-name>
# User guide: https://secureanycloud.com/stacks/projeqtor-13-1-1/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. nginx answers only to the names in the certificate. The name is fixed at first boot. If you stop and start the instance without an Elastic IP and its public address changes, run sudo projeqtor-set-hostname with the new name (Step 10). A better option is to give the instance an Elastic IP or your own DNS name.
Sign in
Open the PROJEQTOR_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 10). Sign in as admin with the password from Step 4:
ProjeQtOr opens its Start Guide, which walks you through the first things to set up: clients, contacts, resources and users, then planning and follow-up. The Users box lists the two seeded accounts. The guest account is closed and locked on this image:
To choose your own password, open the user menu (admin, top right) and select Change password. The password must be at least 12 characters. Then delete or update PROJEQTOR_ADMIN_PASSWORD in /root/projeqtor-credentials.txt so that it does not hold an old password.
If you lose the admin password, set a new one from the instance. Without an option, the command generates a random password and writes it to the credentials file. With --stdin, it reads the password you type (20 to 72 letters and digits). Either way, it unlocks admin and signs every session out:
sudo projeqtor-set-password
sudo projeqtor-set-password --stdinPlan a project
Create your project under Projects. Add its activities and milestones under Activities and Milestones, link them with predecessors, allocate resources, and then open Planning. Select the calculator button (Calculate planning). ProjeQtOr schedules every activity from its work, its dependencies and the resources' availability, and draws the Gantt chart. The example below is a demonstration project with five chained activities and a milestone:
The project selector at the top (Project:) limits every list and the Gantt chart to one project, or shows them all. Ticketing, Agile scrum, Follow-up, Financial and Reports in the main menu cover the rest of the suite. Configuration holds the profiles, access rights, workflows and global parameters.
The rich text editor and who can use it
Descriptions, results and notes use a rich text editor, which is CKEditor 4.22.1 on this image:
Text written in the editor is stored as formatted HTML and shown to every user who can open the item. The image removes the editor's Source button and source mode, and runs CKEditor's content filter with an explicit list of allowed elements. As a result, scripts, styles, frames, embedded objects, forms, SVG and event handlers are dropped, and javascript: and vbscript: links are removed:
grep -oE 'version:"[0-9.]+' /var/www/projeqtor/external/ckeditor/ckeditor.js | head -1 | sed 's/version:"/CKEditor /' C=/var/www/projeqtor/external/ckeditor/projeqtorConfig.js
grep -q "'Source'" "$C" && echo "Source button: present" || echo "Source button: none"
grep -oE "config.removePlugins='[^']*'" "$C"CKEditor 4.22.1 Source button: none config.removePlugins='magicline,exportpdf,sourcearea,sourcedialog'That filter runs in the user's browser, so a user who sends requests to the server by hand can still store HTML that other users then see. Give accounts that can create or edit items only to people you trust, such as colleagues. Use ProjeQtOr's profiles (Configuration, Access rights) to keep everyone else read-only.
Users, e-mail and what the server contacts
Users. Add each person under Configuration, Users (or as a resource marked as a user) with their own profile. There is no self-registration: only an administrator creates accounts. Sign-in with an LDAP directory is off. An administrator can turn it on under Configuration, Global parameters. SAML single sign-on is not available on this image.
E-mail. ProjeQtOr sends notifications and alerts by e-mail, and can read replies from a mailbox. Set your SMTP server under Configuration, Global parameters, Mailing. Amazon SES works as the relay (port 587 with STARTTLS). Allow outbound port 587 if you restrict egress.
The cron. Alerts, notifications, imports and the mailbox reader run in ProjeQtOr's own cron loop. On this image, the loop is a systemd service that runs as projeqtor from the PHP command line, not as a web request:
systemctl is-active projeqtor-cron
systemctl show projeqtor-cron -p Useractive User=projeqtorWhat the server contacts. ProjeQtOr connects only to the SMTP, IMAP and LDAP servers an administrator configures, plus one update check. When admin signs in, the server asks projeqtor.org for the newest release number (a plain request that sends no data about your instance). To turn that off, open Configuration, Global parameters, System, and set check for new version to No in the Miscellaneous section. Nothing else is sent. The browser news and statistics call is blocked and switched off, as these checks show.
H=$(sudo grep '^PROJEQTOR_URL=' /root/projeqtor-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#')
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/view/main.php" | tr -d '\r' \
| grep -iE '^(content-security-policy|x-frame-options|x-content-type-options|referrer-policy|set-cookie: PHPSESSID)' \
| sed -E 's/PHPSESSID=[^;]*/PHPSESSID=<session>/'
sudo mysql -N -B projeqtor -e "SELECT parameterCode, parameterValue FROM parameter WHERE idUser IS NULL AND idProject IS NULL AND parameterCode IN ('allowSendingStatistics', 'rememberMe', 'paramPasswordMinLength', 'paramHttps') ORDER BY parameterCode;"set-cookie: PHPSESSID=<session>; path=/; secure; HttpOnly; SameSite=Lax x-content-type-options: nosniff x-frame-options: SAMEORIGIN referrer-policy: strict-origin-when-cross-origin content-security-policy: connect-src 'self' allowSendingStatistics NO paramHttps on paramPasswordMinLength 12 rememberMe NOThe REST API
ProjeQtOr's REST API (/api/) lets scripts read and write ProjeQtOr items as a user. It does not check a password itself. It trusts the user name the web server hands it after the web server has authenticated the caller, which is why it answers 404 on this image. To use it, put it behind nginx Basic authentication with a password file that only nginx reads. Each API user name must be a ProjeQtOr user, and the caller gets that user's access rights.
Create the password file with an API password for each user (here admin). Use a long random password that is not the user's ProjeQtOr password. openssl reads the password you type and stores only its hash:
printf 'admin:%s\n' "$(openssl passwd -6 -stdin)" | sudo install -m 0640 -o root -g www-data /dev/stdin /etc/nginx/projeqtor-api.htpasswdAdd the API location to nginx and include it in the ProjeQtOr site, just before the final location / { return 404; }:
sudo tee /etc/nginx/snippets/projeqtor-api.conf >/dev/null <<'CONF'
# ProjeQtOr REST API behind nginx Basic authentication. location ^~ /api/ { auth_basic "ProjeQtOr API"; auth_basic_user_file /etc/nginx/projeqtor-api.htpasswd; rewrite ^/api/(.*)$ /api/index.php?uri=$1 break; include /etc/nginx/snippets/sac-projeqtor-fastcgi.conf; fastcgi_param REMOTE_USER $remote_user; } CONF
sudo sed -i 's|^ location / { return 404; }$| include /etc/nginx/snippets/projeqtor-api.conf;\n location / { return 404; }|' /etc/nginx/sites-available/sac-projeqtor
sudo nginx -t && sudo systemctl reload nginxA call without the API password is refused with 401. With the password, ProjeQtOr answers in JSON (this call lists the projects the user can see):
curl -s -o /dev/null -w '%{http_code}\n' https://<your-host>/api/Project/all 401
curl -s -u admin https://<your-host>/api/Project/all {"identifier":"id", "items":[ ... ] }A closed or locked ProjeQtOr user is refused even with a valid API password. Writes (PUT, POST, DELETE) also need the data encrypted with the user's API key, which appears read-only on the user's form under Configuration, Users. See ProjeQtOr's API documentation for details. Keep the API to the networks that need it (for example, with nginx allow and deny lines in the same location). To close the API again, remove the include line and reload nginx.
Your own DNS name and certificate
Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name that ProjeQtOr is served as. The command generates a new self-signed certificate for the name, points nginx and ProjeQtOr's links (including the password reset link) at it, updates the URL in /root/projeqtor-credentials.txt, and keeps the name across reboots:
sudo projeqtor-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 nginx. Running projeqtor-set-hostname again replaces it with a self-signed one:
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/projeqtor.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/projeqtor.key
sudo nginx -t && sudo systemctl reload nginxSecurity checks
nginx serves only the application. The installer and self-update endpoints, the database scripts, the plugin directory, the stored files and the closed API and SAML paths answer 404. Plain HTTP redirects to HTTPS, and a request for a name the instance does not serve gets no answer at all. Requests to ProjeQtOr's /tool/ endpoints that do not come from ProjeQtOr's own pages are refused with 403 before routing, so the check sends the header that a browser sends from those pages.
H=$(sudo grep '^PROJEQTOR_URL=' /root/projeqtor-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/#\1#') for p in / /view/main.php /api/Project/all /sso/ /tool/config.php /tool/installAutoDownload.php /db/projeqtor_V13.1.1.sql /plugin/ /files/attach/ /files/logs/; do curl -sk -o /dev/null -w "%{http_code} $p\n" -H 'Sec-Fetch-Site: same-origin' --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/" || true200 / 200 /view/main.php 404 /api/Project/all 404 /sso/ 404 /tool/config.php 404 /tool/installAutoDownload.php 404 /db/projeqtor_V13.1.1.sql 404 /plugin/ 404 /files/attach/ 404 /files/logs/ http:// -> 301 unknown host name -> 000admin is the only account that can sign in. The guest account is closed and locked:
sudo mysql -N -B projeqtor -e "SELECT CONCAT(name, ': locked ', IF(locked=1, 'yes', 'no'), ', closed ', IF(idle=1, 'yes', 'no')) FROM resource WHERE isUser = 1 ORDER BY id;"admin: locked no, closed no guest: locked yes, closed yesProjeQtOr's database account has no password. MariaDB accepts it only over the local socket from processes running as projeqtor, 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 = 'projeqtor'; SELECT CONCAT('skip_networking: ', @@skip_networking);"projeqtor@localhost authenticates by unix_socket, password set: no skip_networking: ONProjeQtOr reports version 13.1.1 in its code and its database:
grep -oE '^\$version="V[0-9.]+"' /var/www/projeqtor/tool/projeqtor.php
sudo mysql -N -B projeqtor -e "SELECT CONCAT('database: ', parameterValue) FROM parameter WHERE parameterCode = 'dbVersion';"$version="V13.1.1" database: V13.1.1The certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^PROJEQTOR_URL=' /root/projeqtor-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 subjectAltNamesubject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = sac 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:localhostNone of the services can reach the instance metadata service, so an SMTP, IMAP or LDAP address that points at it gets nothing back:
systemctl show nginx php8.3-fpm mariadb projeqtor-cron projeqtor-firstboot -p Id -p IPAddressDenyIPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=nginx.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=php8.3-fpm.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=mariadb.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=projeqtor-cron.service IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 Id=projeqtor-firstboot.servicesudo projeqtor-php <script.php> runs a PHP script as ProjeQtOr with the same restriction.
Backup and maintenance
Everything you enter is in the MariaDB database projeqtor (/var/lib/mysql); attachments, documents, uploaded images and ProjeQtOr's logs are under /var/lib/projeqtor/files. Both are on the root volume, so the simplest backup is an EBS snapshot of it. To make a consistent copy while ProjeQtOr is running:
sudo mariadb-dump --single-transaction --routines projeqtor | gzip > <backup-dir>/projeqtor-$(date +%F).sql.gz
sudo tar -C /var/lib/projeqtor -czf <backup-dir>/projeqtor-files-$(date +%F).tar.gz filesThe dump holds every password hash and API key, so store it 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.
ProjeQtOr's web self-updater and plugin installer are switched off on this image, because the code is read-only to PHP. New ProjeQtOr releases arrive as new versions of this listing. Contact support if you would like help moving your data to a new version. Operating system security updates install automatically. Run sudo apt-get update && sudo apt-get upgrade to apply everything else. nginx, PHP-FPM and MariaDB restart cleanly, and ProjeQtOr keeps all of its data.
Component Path
ProjeQtOr /var/www/projeqtor (owned by root, read only to PHP)
Configuration /etc/projeqtor/parameters.php (mode 0640)
Database MariaDB database projeqtor (/var/lib/mysql)
Files, sessions and logs /var/lib/projeqtor
nginx site /etc/nginx/sites-available/sac-projeqtor
Certificate and key /etc/nginx/ssl/projeqtor.crt, /etc/nginx/ssl/projeqtor.key
Cron projeqtor-cron.service
Helpers /usr/local/sbin/projeqtor-set-password, projeqtor-set-hostname, projeqtor-php
Credentials /root/projeqtor-credentials.txt (mode 0600)
Source and changes /usr/share/doc/projeqtor/SOURCE.txt
Security recommendations
- Allow port 22 from your admin network only, and ports 80 and 443 only from the networks your users work from.
- Use your own DNS name and a certificate that your users' browsers trust (Step 10).
- Give each person their own ProjeQtOr user and the narrowest profile that fits. Give accounts that can create or edit items only to people you trust (Step 7). Change the
adminpassword if it may have been shared. - Leave
/api/closed unless you need it. If you open it, put it behind nginx authentication and limit it to the networks that call it (Step 9). Leave/sso/closed. - Keep the operating system patched, and snapshot the root volume regularly.
Support and licensing
ProjeQtOr is open source under the GNU Affero General Public License version 3 or later, by the ProjeQtOr team. There is no per-user or per-CPU licence fee. The AGPL gives every user of your instance the right to its source. The image keeps the complete source in /var/www/projeqtor, and /usr/share/doc/projeqtor/SOURCE.txt names the release it came from and lists every file the image removes or changes (the editor hardening is marked SecureAnyCloud in the changed file). The AMI price covers SecureAnyCloud's hardening, first boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- 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.
Deploy on AWS
Launch ProjeQtOr 13.1.1 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