
This article provides a guide demonstrating how to install ITFlow on Debian VPS.
What is ITFlow?
ITFlow is a free, open-source professional services automation platform designed primarily for managed service providers and IT departments. It combines client documentation, asset tracking, password management, ticketing, invoicing, accounting, domain and SSL monitoring, and a client portal in one application.
The ITFlow developers currently recommend installing the application on a clean Debian 13 server using their official installation script. The script configures Apache, PHP, MariaDB, TLS, scheduled tasks, and the ITFlow application itself.
This guide covers:
- VPS and DNS preparation
- Server hardening
- The recommended automated installation
- Verification and troubleshooting
- Email and cron configuration
- Backups
- Updates
- A manual installation alternative
Compare Debian VPS Plans
How to Install ITFlow on Debian VPS
To install ITFlow on Debian VPS, follow the steps below:
-
Deployment architecture
The finished deployment will use:
Component Purpose Debian 13 Server operating system Apache Web server PHP ITFlow application runtime MariaDB ITFlow database Let’s Encrypt HTTPS certificate Cron Background maintenance and mail processing UFW Host firewall Fail2ban Protection against repeated login attacks The official documentation recommends Apache with MariaDB. It specifically recommends MariaDB over MySQL because some ITFlow pages may not function correctly with MySQL.
-
VPS requirements
For a small ITFlow deployment, a reasonable starting configuration is:
- 2 CPU cores
- 2–4 GB RAM
- 30–50 GB SSD storage
- Debian VPS
- One public IPv4 address
- A fully qualified domain name
- Root or sudo access
- Ports TCP 22, 80, and 443 available
The official installation-script requirements are:
- A clean Debian 13 installation
- A public IP address
- Incoming TCP ports 80 and 443 (See: Guide)
- A domain name pointing to the server
For example:
itflow.example.com
Because ITFlow can contain passwords, network diagrams, client records, billing information, and infrastructure documentation, it should not be deployed on an unsecured or shared hosting account.
-
Configure DNS
Create an
Arecord for the ITFlow hostname.Example:
Type Name Value TTL A itflow 203.0.113.25 300 This produces:
itflow.example.com
Replace
203.0.113.25with the VPS public IPv4 address.If the server has IPv6, you may also create an
AAAArecord:Type Name Value AAAA itflow Server IPv6 address Verify the record from your workstation:
dig +short itflow.example.com
Or:
nslookup itflow.example.com
The result should show the VPS IP address.
Do not begin certificate issuance until the domain resolves correctly. Let’s Encrypt must be able to reach the server over port 80 or 443.
-
Connect to the server
ssh root@203.0.113.25
Alternatively, connect as a sudo-enabled user:
ssh administrator@203.0.113.25
Then obtain a root shell:
sudo -i
Confirm the operating system:
cat /etc/os-release
You should see output identifying Debian 13.
Check the hostname:
hostnamectl
Set a meaningful hostname when necessary:
hostnamectl set-hostname itflow.example.com
Add the hostname to
/etc/hosts:nano /etc/hosts
Use an entry similar to:
127.0.0.1 localhost 203.0.113.25 itflow.example.com itflow
Do not remove the standard IPv6 entries.
-
Update Debian
Update the package indexes and install available upgrades:
apt update apt full-upgrade -y
Install basic administration utilities:
apt install -y \ ca-certificates \ curl \ wget \ git \ unzip \ nano \ sudo \ ufw \ fail2ban \ dnsutilsReboot if the kernel or core system packages were updated:
reboot
Reconnect after the server returns online.
-
Create an administrative user
Although the official script is run as root, routine server administration should use a non-root account.
Create an administrator:
adduser itflowadmin
Grant sudo privileges:
usermod -aG sudo itflowadmin
Confirm membership:
id itflowadmin
The output should include the
sudogroup.Copy the existing root SSH authorization when appropriate:
install -d -m 700 -o itflowadmin -g itflowadmin /home/itflowadmin/.ssh cp /root/.ssh/authorized_keys /home/itflowadmin/.ssh/ chown itflowadmin:itflowadmin /home/itflowadmin/.ssh/authorized_keys chmod 600 /home/itflowadmin/.ssh/authorized_keys
Open a second terminal and verify that you can sign in:
ssh itflowadmin@203.0.113.25
Do not disable root or password authentication until key-based access has been tested successfully.
-
Configure the firewall
Allow SSH before enabling the firewall:
ufw allow OpenSSH
Allow web traffic:
ufw allow 80/tcp ufw allow 443/tcp
Enable the firewall:
ufw enable
Review the rules:
ufw status verbose
Expected rules include:
22/tcp ALLOW 80/tcp ALLOW 443/tcp ALLOW
When SSH runs on a custom port, allow that port before enabling UFW:
ufw allow 2222/tcp
Recommended installation method
-
Download the official installer
The official project recommends its installation script as the preferred self-hosting method for Debian and Ubuntu.
Move to a safe working directory:
cd /root
Download the installer:
wget -O itflow_install.sh \ https://github.com/itflow-org/itflow-install-script/raw/main/itflow_install.sh
Confirm that the file exists:
ls -lh itflow_install.sh
Before running a remotely downloaded script as root, inspect it:
less itflow_install.sh
You can also check its syntax:
bash -n itflow_install.sh
No output from
bash -nnormally means no shell syntax errors were found.Optionally calculate and retain a checksum:
sha256sum itflow_install.sh
This does not establish trust by itself, but it records exactly which script was executed.
-
Run the installer
Run the official script:
bash itflow_install.sh
Follow the interactive prompts.
Depending on the current installer version, it may ask for information such as:
- Domain name
- Stable or development branch
- SSL configuration
- Certificate email address
- Database password
- Application administrator details
Use the production hostname:
itflow.example.com
Choose the stable release or production branch unless you are intentionally building a testing server.
Use a valid email address for Let’s Encrypt expiration and security notifications.
Generate a strong database password with:
openssl rand -base64 36
Store that password in a secure password manager.
The official installer currently clones ITFlow into a domain-specific directory under
/var/www, creates anitflowMariaDB database and user, creates Apache configuration, and installs ITFlow cron jobs. -
Monitor the installation
The installer writes a log to:
/var/log/itflow_install.log
Watch the log from another SSH session:
tail -f /var/log/itflow_install.log
After installation, review it for errors:
less /var/log/itflow_install.log
Search for common failure indicators:
grep -Ei "error|failed|failure|denied" /var/log/itflow_install.log
Not every line containing “error” indicates installation failure, so review the surrounding context.
-
Verify services
Check Apache:
systemctl status apache2 --no-pager
Check MariaDB:
systemctl status mariadb --no-pager
Check cron:
systemctl status cron --no-pager
Enable all three services at boot:
systemctl enable apache2 mariadb cron
Confirm that ports 80 and 443 are listening:
ss -lntp | grep -E ':80|:443'
Expected output should reference Apache.
Test Apache configuration:
apachectl configtest
Expected result:
Syntax OK
-
Open ITFlow
Visit:
https://itflow.example.com
Itflow login If the installer completed the initial application configuration, you should see either:
- The ITFlow login page
- The initial account setup screen
- A final installation/configuration page
Complete the web-based setup when prompted.
You will generally need to provide:
- Company name
- Administrator name
- Administrator email address
- A strong administrator password
- Locale and currency
- Database details, if they were not created automatically
Use a unique administrator password. Do not reuse the server root password or database password.
-
Confirm HTTPS
Inspect the site from the command line:
curl -I https://itflow.example.com
A successful response will normally include an HTTP status such as:
HTTP/2 200
or:
HTTP/1.1 200 OK
Check the certificate:
openssl s_client \ -connect itflow.example.com:443 \ -servername itflow.example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -datesTest automatic renewal:
certbot renew --dry-run
List installed certificates:
certbot certificates
Check the Certbot timer:
systemctl status certbot.timer --no-pager
If the installer used a different ACME client, identify it with:
systemctl list-timers --all | grep -Ei 'certbot|acme|letsencrypt'
Understanding the deployed components
-
Locate the application directory
The current official installer clones ITFlow into:
/var/www/DOMAIN
For this example:
/var/www/itflow.example.com
Confirm it:
ls -la /var/www/itflow.example.com
Store the path in a shell variable for convenience:
ITFLOW_ROOT="/var/www/itflow.example.com"
Check repository status:
sudo -u www-data git -C "$ITFLOW_ROOT" status
Check the active branch:
sudo -u www-data git -C "$ITFLOW_ROOT" branch --show-current
Do not run arbitrary Git commands or force resets on a production instance without taking a database and file backup first.
-
Review file ownership
The Apache user on Debian is normally:
www-data
Check ownership:
stat -c '%U:%G %a %n' "$ITFLOW_ROOT"
Inspect the main configuration file:
stat -c '%U:%G %a %n' "$ITFLOW_ROOT/config.php"
The application directory normally needs to remain writable by
www-databecause ITFlow supports Git-based self-updates. The official manual documentation sets the application ownership towww-data:www-data, uses directory permissions of775, and restrictsconfig.phpto640.A typical configuration is:
chown -R www-data:www-data "$ITFLOW_ROOT" find "$ITFLOW_ROOT" -type d -exec chmod 775 {} \; find "$ITFLOW_ROOT" -type f -exec chmod 664 {} \; chmod 640 "$ITFLOW_ROOT/config.php"Only run those commands if permissions are demonstrably incorrect. Avoid repeatedly changing permissions without understanding the application’s update requirements.
-
Review Apache configuration
List the enabled sites:
apache2ctl -S
Look for files associated with the hostname:
ls -l /etc/apache2/sites-available/ ls -l /etc/apache2/sites-enabled/
View the virtual host:
cat /etc/apache2/sites-available/itflow.example.com.conf
The filename may differ.
A production HTTP virtual host should redirect to HTTPS:
ServerName itflow.example.com Redirect permanent / https://itflow.example.com/The HTTPS host should resemble:
ServerName itflow.example.com DocumentRoot /var/www/itflow.example.com Options FollowSymLinks AllowOverride All Require all granted SSLEngine on SSLCertificateFile /etc/letsencrypt/live/itflow.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/itflow.example.com/privkey.pem ErrorLog ${APACHE_LOG_DIR}/itflow-error.log CustomLog ${APACHE_LOG_DIR}/itflow-access.log combinedTest and reload after any change:
apachectl configtest && systemctl reload apache2
PHP configuration
-
Verify PHP and extensions
Check the PHP version:
php -v
List the extensions:
php -m
Confirm the modules ITFlow commonly requires:
php -m | grep -Ei \ 'curl|gd|intl|mbstring|mysqli|xml|zip'
The official manual installation currently lists:
- PHP
php-intlphp-mysqliphp-curlphp-gdphp-mbstringphp-zipphp-xmllibapache2-mod-php
Install any missing modules:
apt install -y \ php \ php-intl \ php-mysql \ php-curl \ php-gd \ php-mbstring \ php-zip \ php-xml \ libapache2-mod-phpRestart Apache:
systemctl restart apache2
-
Adjust PHP upload limits
ITFlow may be used to store documentation and attachments. The official manual guide suggests:
upload_max_filesize = 500M post_max_size = 500M max_execution_time = 300
Find the active Apache PHP configuration:
php --ini
Determine the installed version:
php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION.PHP_EOL;'
For example:
nano /etc/php/8.4/apache2/php.ini
Search for and change:
upload_max_filesize = 500M post_max_size = 500M max_execution_time = 300 memory_limit = 512M
Also update the CLI configuration so cron tasks receive appropriate limits:
nano /etc/php/8.4/cli/php.ini
Set at least:
memory_limit = 512M max_execution_time = 300
Restart Apache:
systemctl restart apache2
Verify the CLI values:
php -i | grep -E \ 'memory_limit|upload_max_filesize|post_max_size|max_execution_time'
To verify Apache’s PHP configuration, create a temporary file:
echo '<?php phpinfo();' > "$ITFLOW_ROOT/phpinfo.php" chown www-data:www-data "$ITFLOW_ROOT/phpinfo.php"
Visit:
https://itflow.example.com/phpinfo.php
Delete it immediately after testing:
rm -f "$ITFLOW_ROOT/phpinfo.php"
A public
phpinfo()page exposes sensitive server configuration and should never remain accessible.MariaDB configuration
-
Verify the database
Check MariaDB:
mysqladmin ping
Expected result:
mysqld is alive
Open the database console:
mariadb
List databases:
SHOW DATABASES;
Confirm the ITFlow database:
USE itflow; SHOW TABLES;
Exit:
EXIT;
The current installer creates the database using
utf8mb4withutf8mb4_unicode_ci, creates anitflowuser limited tolocalhost, and grants that user access only to the ITFlow database.Verify the character set:
mariadb -N -e \ "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='itflow';"
Expected output:
utf8mb4 utf8mb4_unicode_ci
-
Secure MariaDB
Run:
mariadb-secure-installation
Depending on Debian’s authentication configuration, answer the prompts to:
- Remove anonymous users
- Disable remote root login
- Remove the test database
- Reload privilege tables
Do not expose port 3306 publicly.
Check listening database sockets:
ss -lntp | grep 3306
MariaDB should normally listen only on localhost or not expose an external TCP socket.
Check UFW:
ufw status numbered
There should be no public MariaDB rule.
Cron and background processing
-
Verify ITFlow cron jobs
ITFlow relies on cron for maintenance, ticket email processing, outbound mail queues, domain checks, and certificate checks.
The official installer currently creates:
/etc/cron.d/itflow
Display it:
cat /etc/cron.d/itflow
The current installer defines tasks similar to:
0 2 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/cron.php * * * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/ticket_email_parser.php * * * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/mail_queue.php 0 3 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/domain_refresher.php 0 4 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/certificate_refresher.php
These schedules come from the current official installer and could change in later releases.
Check file ownership and mode:
stat -c '%U:%G %a %n' /etc/cron.d/itflow
Expected values are normally:
root:root 644
Correct them if necessary:
chown root:root /etc/cron.d/itflow chmod 644 /etc/cron.d/itflow
Restart cron:
systemctl restart cron
Check recent cron activity:
journalctl -u cron --since "1 hour ago"
-
Test a cron task manually
Run the general maintenance task as the Apache user:
sudo -u www-data php "$ITFLOW_ROOT/cron/cron.php"
Check the exit status:
echo $?
A status of
0usually indicates success.Test the outbound queue processor:
sudo -u www-data php "$ITFLOW_ROOT/cron/mail_queue.php"
Test the domain refresher:
sudo -u www-data php "$ITFLOW_ROOT/cron/domain_refresher.php"
Do not run email parsers repeatedly on a production system unless inbound email configuration is complete.
Email configuration
-
Configure outbound email
ITFlow uses outbound email for functions including:
- Ticket notifications
- Password-reset emails
- Invoice delivery
- Quote delivery
- Client portal notifications
- System alerts
Sign in as an administrator and locate the email or mail settings under the ITFlow administrative settings.
Enter the SMTP information supplied by your email provider.
Typical settings:
SMTP host: smtp.example.com SMTP port: 587 Encryption: STARTTLS Authentication: Enabled Username: itflow@example.com Password: Application-specific password From address: itflow@example.com From name: Company IT Support
Prefer authenticated SMTP submission over installing a public mail server directly on the VPS.
Send a test message from the ITFlow interface.
Follow the mail queue log while testing:
journalctl -u cron -f
Also inspect Apache errors:
tail -f /var/log/apache2/error.log
Depending on the virtual-host configuration, ITFlow may have a separate log file under
/var/log/apache2/. -
Configure email-to-ticket
ITFlow can retrieve messages from a mailbox and create or update tickets. The official installation documentation considers outbound email and email-to-ticket essential post-installation housekeeping.
Create a dedicated support mailbox, for example:
support@example.com
Use a separate mailbox rather than a personal account.
Typical inbound settings:
Protocol: IMAP Host: imap.example.com Port: 993 Encryption: SSL/TLS Username: support@example.com Password: Application-specific password
Ensure the email parser cron task is present:
grep ticket_email_parser /etc/cron.d/itflow
Test it manually after configuring the mailbox:
sudo -u www-data php \ "$ITFLOW_ROOT/cron/ticket_email_parser.php"
Send a test message from an external address and verify that a ticket appears.
Initial ITFlow configuration
-
Complete the organization settings
After the first login, configure:
- Organization name
- Address and contact information
- Logo and branding
- Default locale
- Time zone
- Date format
- Default currency
- Tax rates
- Invoice numbering
- Quote numbering
- Ticket numbering
- Payment terms
- Client portal branding
Make sure the application and Debian use the same time zone.
Check Debian:
timedatectl
List time zones:
timedatectl list-timezones
Set one, for example:
timedatectl set-timezone America/Chicago
Confirm:
timedatectl
-
Create individual administrator accounts
Do not share a single global administrator login.
Create separate accounts for each team member and assign only the permissions required for their role.
Recommended role separation:
- System administrator
- Help-desk technician
- Billing staff
- Read-only auditor
- Client portal user
Enable multifactor authentication wherever supported.
Remove or disable unused accounts promptly.
-
Protect sensitive ITFlow data
ITFlow may hold:
- Administrative passwords
- API credentials
- Device login details
- Network diagrams
- Client contact data
- Financial records
- Internal notes
- Domain and certificate details
Apply these minimum controls:
- Require HTTPS.
- Use unique administrator accounts.
- Use long, randomly generated passwords.
- Enable multifactor authentication.
- Restrict access through a VPN or IP allowlist when practical.
- Back up the master encryption key separately.
- Encrypt backup storage.
- Test restorations regularly.
- Review account and application logs.
- Keep Debian and ITFlow updated.
The ITFlow documentation explicitly warns that no system is risk-free and recommends careful judgment before storing highly confidential information.
Backups
-
Identify what must be backed up
A complete ITFlow backup should include:
- MariaDB database
- Application files
- Uploaded documents and attachments
config.php- The ITFlow master encryption key
- Apache virtual-host configuration
- Let’s Encrypt configuration, when useful
- Cron configuration
- A record of the installed branch/version
The master encryption key is particularly important. Losing it may make encrypted credentials or other protected data unrecoverable. The official post-installation checklist specifically calls out backups, especially of the master encryption key.
Do not keep the only backup on the ITFlow server.
-
Create a local backup script
Create a protected backup directory:
install -d -m 700 /var/backups/itflow
Create the script:
nano /usr/local/sbin/backup-itflow
Add:
#!/bin/bash set -Eeuo pipefail umask 077 DOMAIN="itflow.example.com" APP_DIR="/var/www/${DOMAIN}" BACKUP_ROOT="/var/backups/itflow" STAMP="$(date +'%Y-%m-%d_%H-%M-%S')" DEST="${BACKUP_ROOT}/${STAMP}" DB_NAME="itflow" mkdir -p "$DEST" echo "Backing up MariaDB..." mariadb-dump \ --single-transaction \ --quick \ --routines \ --triggers \ --events \ "$DB_NAME" | gzip > "${DEST}/itflow-database.sql.gz" echo "Backing up application files..." tar \ --acls \ --xattrs \ -czf "${DEST}/itflow-files.tar.gz" \ -C /var/www \ "$DOMAIN" echo "Backing up server configuration..." tar \ -czf "${DEST}/server-config.tar.gz" \ /etc/apache2 \ /etc/cron.d/itflow \ /etc/letsencrypt 2>/dev/null || true echo "Recording application information..." git -C "$APP_DIR" rev-parse HEAD \ > "${DEST}/git-commit.txt" 2>/dev/null || true git -C "$APP_DIR" branch --show-current \ > "${DEST}/git-branch.txt" 2>/dev/null || true php -v > "${DEST}/php-version.txt" mariadb --version > "${DEST}/mariadb-version.txt" echo "Creating checksums..." ( cd "$DEST" sha256sum \ itflow-database.sql.gz \ itflow-files.tar.gz \ server-config.tar.gz \ > SHA256SUMS ) echo "Removing local backups older than 14 days..." find "$BACKUP_ROOT" \ -mindepth 1 \ -maxdepth 1 \ -type d \ -mtime +14 \ -exec rm -rf {} + echo "Backup complete: $DEST"Make it executable:
chmod 700 /usr/local/sbin/backup-itflow
Run it:
/usr/local/sbin/backup-itflow
List the results:
find /var/backups/itflow -maxdepth 2 -type f -ls
Verify checksums:
cd /var/backups/itflow/$(ls -1 /var/backups/itflow | tail -1) sha256sum -c SHA256SUMS
Transfer the backup to encrypted remote storage using a suitable backup platform, object-storage client, or off-server synchronization tool.
-
Schedule backups
Create:
nano /etc/cron.d/itflow-backup
Add:
30 1 * * * root /usr/local/sbin/backup-itflow >> /var/log/itflow-backup.log 2>&1
Set permissions:
chown root:root /etc/cron.d/itflow-backup chmod 644 /etc/cron.d/itflow-backup
Restart cron:
systemctl restart cron
Inspect the log:
tail -n 100 /var/log/itflow-backup.log
A backup is not reliable until you have restored it successfully on a separate test server.
Updates and maintenance
-
Update Debian safely
Install security and package updates regularly:
apt update apt upgrade -y
Review packages that require a reboot:
test -f /var/run/reboot-required && cat /var/run/reboot-required
Reboot during an appropriate maintenance window:
reboot
After rebooting, verify:
systemctl --failed systemctl status apache2 mariadb cron --no-pager curl -I https://itflow.example.com
-
Update ITFlow
ITFlow is designed to update through Git, and its web interface may offer an application update function.
Before every application update:
- Read the release notes.
- Take a database backup.
- Take an application-file backup.
- Record the current Git commit.
- Confirm sufficient disk space.
- Perform the update during a maintenance window.
- Test login, ticketing, billing, and cron afterward.
Record the current commit:
sudo -u www-data git -C "$ITFLOW_ROOT" rev-parse HEAD
Check repository status:
sudo -u www-data git -C "$ITFLOW_ROOT" status
The working tree should normally be clean before updating.
Check for remote changes:
sudo -u www-data git -C "$ITFLOW_ROOT" fetch --prune
Review the active branch:
sudo -u www-data git -C "$ITFLOW_ROOT" branch --show-current
Use ITFlow’s built-in updater when available and recommended by the project. Avoid blindly running
git pullwhen the application reports local modifications or requires database migrations.After updating:
apachectl configtest systemctl restart apache2 sudo -u www-data php "$ITFLOW_ROOT/cron/cron.php"
Test:
- Administrator login
- Client portal login
- Ticket creation
- Outbound email
- Attachments
- Invoice display
- Domain monitoring
- Certificate monitoring
-
Monitor available storage
Check storage:
df -h
Check inode usage:
df -i
Find large directories:
du -xhd1 /var | sort -h
Check ITFlow storage use:
du -sh "$ITFLOW_ROOT"
Check database size:
mariadb -N -e " SELECT table_schema, ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema='itflow' GROUP BY table_schema; "Ensure backups, Apache logs, attachments, and database growth cannot exhaust the root filesystem.
-
Configure log rotation
Debian already rotates Apache and MariaDB logs, but your custom backup log should also be rotated.
Create:
nano /etc/logrotate.d/itflow-backup
Add:
/var/log/itflow-backup.log { weekly rotate 12 compress delaycompress missingok notifempty create 0640 root adm }Test the configuration:
logrotate -d /etc/logrotate.d/itflow-backup
Additional security hardening
-
Configure Fail2ban
Enable and start Fail2ban:
systemctl enable --now fail2ban
Create a local SSH jail:
nano /etc/fail2ban/jail.d/sshd.local
Add:
[sshd] enabled = true port = ssh backend = systemd maxretry = 5 findtime = 10m bantime = 1h
Restart:
systemctl restart fail2ban
Check status:
fail2ban-client status fail2ban-client status sshd
If SSH uses a custom port, replace
port = sshwith that port number. -
Harden SSH
Edit:
nano /etc/ssh/sshd_config
Once key-based access has been tested, consider:
PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no MaxAuthTries 4 X11Forwarding no
Validate the configuration:
sshd -t
Reload SSH:
systemctl reload ssh
Keep the current SSH session open while testing a second login. A configuration mistake could otherwise lock you out of the server.
-
Add HTTP security headers
Enable the headers module:
a2enmod headers
Inside the HTTPS virtual host, add:
Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()" Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Be careful with HSTS. Only enable
includeSubDomainswhen every subdomain is served securely over HTTPS.Test and reload:
apachectl configtest && systemctl reload apache2
Check the headers:
curl -I https://itflow.example.com
Do not add a restrictive Content Security Policy without testing ITFlow thoroughly, because it may interfere with scripts, styles, editors, or embedded content.
-
Restrict access by IP when appropriate
For an internal-only deployment, restrict Apache to trusted addresses:
AllowOverride All Require ip 198.51.100.10 Require ip 192.0.2.0/24This may interfere with client portal access, email webhooks, payment integrations, remote staff, and API access.
A VPN such as WireGuard or a properly configured identity-aware proxy is often more flexible than a fixed IP allowlist.
Troubleshooting
-
Website does not load
Check DNS:
dig +short itflow.example.com
Check services:
systemctl status apache2 mariadb --no-pager
Check listening ports:
ss -lntp | grep -E ':80|:443'
Check the firewall:
ufw status verbose
Check Apache syntax:
apachectl configtest
Review errors:
tail -n 100 /var/log/apache2/error.log journalctl -u apache2 -n 100 --no-pager
-
Apache shows the default page
Inspect virtual-host selection:
apache2ctl -S
Disable the Debian default sites when they conflict:
a2dissite 000-default.conf a2dissite default-ssl.conf
Enable the ITFlow sites:
a2ensite itflow.example.com.conf a2ensite itflow.example.com-ssl.conf
The exact filenames may differ.
Reload:
apachectl configtest && systemctl reload apache2
-
HTTP 403 Forbidden
Check ownership:
namei -l "$ITFLOW_ROOT"
Check Apache directory permissions:
grep -R -n \ "/var/www/itflow.example.com" \ /etc/apache2/sites-available/
The relevant directory block should include:
Require all granted
Correct application ownership when necessary:
chown -R www-data:www-data "$ITFLOW_ROOT"
Then:
systemctl reload apache2
-
HTTP 500 Internal Server Error
Review:
tail -f /var/log/apache2/error.log
Check PHP:
php -v php -m
Check configuration syntax:
php -l "$ITFLOW_ROOT/config.php"
Check Apache modules:
apache2ctl -M | grep -E 'php|rewrite|ssl|headers'
Enable commonly required modules:
a2enmod rewrite ssl headers systemctl restart apache2
-
Database connection failed
Verify MariaDB:
systemctl status mariadb --no-pager mysqladmin ping
Test the ITFlow database user:
mariadb -u itflow -p itflow
Review application settings without publicly displaying the password:
sudo -u www-data grep -n \ 'db_host\|db_name\|db_user' \ "$ITFLOW_ROOT/config.php"
Check grants from a root database session:
SHOW GRANTS FOR 'itflow'@'localhost';
Expected privileges should be limited to:
itflow.*
-
SSL certificate issuance fails
Confirm DNS:
dig +short A itflow.example.com dig +short AAAA itflow.example.com
An incorrect
AAAArecord can cause validation failure even when the IPv4 record is correct.Confirm port 80 is accessible:
ss -lntp | grep ':80' ufw status
Review Certbot logs:
journalctl -u certbot --no-pager tail -n 100 /var/log/letsencrypt/letsencrypt.log
Retry after resolving DNS and firewall problems:
certbot --apache -d itflow.example.com
-
Cron tasks are not running
Check cron:
systemctl status cron --no-pager
Inspect the file:
cat -A /etc/cron.d/itflow
Check permissions:
stat /etc/cron.d/itflow
Verify the PHP path:
command -v php
Verify referenced scripts:
ls -l "$ITFLOW_ROOT/cron/"
Run a task manually:
sudo -u www-data php "$ITFLOW_ROOT/cron/cron.php"
Review cron logs:
journalctl -u cron --since today
-
Outbound mail is stuck
Confirm the mail queue task exists:
grep mail_queue /etc/cron.d/itflow
Run it manually:
sudo -u www-data php "$ITFLOW_ROOT/cron/mail_queue.php"
Test the SMTP endpoint:
openssl s_client \ -starttls smtp \ -connect smtp.example.com:587 \ -servername smtp.example.comCommon causes include:
- Incorrect SMTP password
- Wrong encryption mode
- Provider requiring an application password
- Outbound port 25 blocked by the VPS provider (Hint: Use Rad Web Hosting VPS to avoid)
- Incorrect port 465 versus 587
- Sender address not authorized
- Expired OAuth or SMTP credentials
Use port 587 with STARTTLS unless the mail provider specifies otherwise.
Manual installation alternative
The automated script is the recommended method. The following condensed procedure is useful when you require a custom Apache virtual host or need to understand the underlying deployment.
-
Install the LAMP components
apt update apt install -y \ apache2 \ mariadb-server \ php \ php-intl \ php-mysql \ php-curl \ php-gd \ php-mbstring \ php-zip \ php-xml \ libapache2-mod-php \ git \ whois \ certbot \ python3-certbot-apacheEnable modules:
a2enmod rewrite ssl headers systemctl restart apache2
These packages reflect the current official manual installation requirements.
-
Create the database
Generate a password:
DB_PASSWORD="$(openssl rand -base64 36)" printf '%s\n' "$DB_PASSWORD"
Store it securely, then open MariaDB:
mariadb
Run:
CREATE DATABASE itflow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'itflow'@'localhost' IDENTIFIED BY 'REPLACE_WITH_STRONG_PASSWORD'; GRANT ALL PRIVILEGES ON itflow.* TO 'itflow'@'localhost'; FLUSH PRIVILEGES; EXIT; -
Clone ITFlow
Create the document root:
mkdir -p /var/www/itflow.example.com
Clone the application:
git clone \ https://github.com/itflow-org/itflow.git \ /var/www/itflow.example.comSet ownership:
chown -R www-data:www-data /var/www/itflow.example.com
Set permissions:
find /var/www/itflow.example.com \ -type d \ -exec chmod 775 {} \; find /var/www/itflow.example.com \ -type f \ -exec chmod 664 {} \;The official manual documentation clones the repository directly into the web root and grants ownership to
www-data. -
Create the Apache virtual host
Create:
nano /etc/apache2/sites-available/itflow.example.com.conf
Add:
ServerName itflow.example.com DocumentRoot /var/www/itflow.example.com Options FollowSymLinks AllowOverride All Require all granted ErrorLog ${APACHE_LOG_DIR}/itflow-error.log CustomLog ${APACHE_LOG_DIR}/itflow-access.log combinedEnable it:
a2ensite itflow.example.com.conf a2dissite 000-default.conf apachectl configtest systemctl reload apache2
-
Obtain the TLS certificate
Run:
certbot --apache \ -d itflow.example.com \ --redirectEnter a valid email address and accept the terms when prompted.
Test renewal:
certbot renew --dry-run
-
Complete the browser installer
Open:
https://itflow.example.com
Provide:
Database host: localhost Database name: itflow Database username: itflow Database password: the generated password
Create the primary administrator account.
After setup, protect the configuration:
chown www-data:www-data /var/www/itflow.example.com/config.php chmod 640 /var/www/itflow.example.com/config.php
-
Create the cron file manually
Create:
nano /etc/cron.d/itflow
Add:
0 2 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/cron.php * * * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/ticket_email_parser.php * * * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/mail_queue.php 0 3 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/domain_refresher.php 0 4 * * * www-data /usr/bin/php /var/www/itflow.example.com/cron/certificate_refresher.php
Set permissions:
chown root:root /etc/cron.d/itflow chmod 644 /etc/cron.d/itflow systemctl restart cron
This matches the task schedule generated by the current official installer.
Final verification checklist
Run:
systemctl is-active apache2 systemctl is-active mariadb systemctl is-active cron apachectl configtest ufw status curl -I https://itflow.example.com certbot renew --dry-run sudo -u www-data php /var/www/itflow.example.com/cron/cron.php
Then verify through the browser:
- HTTPS is valid.
- Administrator login works.
- Multifactor authentication is enabled.
- A test client can be created.
- A test ticket can be opened.
- Outbound email works.
- Email-to-ticket works, when enabled.
- Attachments upload successfully.
- The client portal loads.
- Domain and SSL checks run.
- Database and file backups complete.
- A backup has been copied off the VPS.
- The master encryption key is stored securely off-server.
- A restoration procedure has been tested.
Once those checks pass, the ITFlow server is ready for production onboarding.
Conclusion
You now know how to install ITFlow on Debian VPS.










