Skip to main content

iNetPanel Changelog

Every iNetPanel release — new features, fixes, and security updates — sourced from GitHub.

v1.27.3 Latest August 24, 2026

v1.27.3 — Hotfix: the weekly Cloudflare RemoteIP refresh deleted its o

Hotfix: the weekly Cloudflare RemoteIP refresh deleted its own Apache config every Monday. /etc/cron.d files do not inherit PATH from /etc/crontab, so apache2ctl and the a2* helpers (all in /usr/sbin) were unreachable under cron; the script read that as its own config being invalid and removed it, leaving a dangling symlink. After the next Apache reload mod_remoteip went inert, every tunnelled request appeared to come from 127.0.0.1, and because fail2ban's ignoreip covers 127.0.0.1/8 that made all tunnelled traffic invisible to every jail. The script now sets PATH itself, verifies its tools before writing anything, checks install's exit status, and no longer assumes a failing configtest is its own fault. Recommended for every install behind a Cloudflare Tunnel.

v1.27.2 August 22, 2026

v1.27.2 — Hotfix: restores admin hook scripts, which 1.27.1 silently d

Hotfix: restores admin hook scripts, which 1.27.1 silently disabled. 1.27.1 repointed the hook sudoers grant out of /tmp but left three callers creating their script there, so the sudo invocation no longer matched and every add_domain/delete_domain hook failed to run. Fails closed, so there is no security impact, and installs with no hook script enabled are unaffected. Upgrade immediately if you use hook scripts.

v1.27.1 August 21, 2026

v1.27.1 — Security: privileged-command input is no longer staged in wo

Security: privileged-command input is no longer staged in world-writable /tmp. Same root cause as GHSA-mjmx-xpqq-p2h8 in a different set of sinks — a local hosting tenant could plant fixed, predictable filenames in /tmp and have a root cp follow a symlink (arbitrary root file read) or a root bash execute their script (arbitrary root code execution). All staging moves to /var/lib/inetpanel/staging, owner-only 0700, with unlink-then-O_EXCL writes. Upgrade recommended for any server with untrusted hosting accounts.

v1.27.0 August 21, 2026

v1.27.0 — External exposure scanning via qsa.sh: run a real external p

External exposure scanning via qsa.sh: run a real external port and vulnerability scan of your public IP from the Firewall page or the CLI, with change monitoring and Discord/Slack webhook alerts. Also fixes CLI deployment under systemd ProtectSystem, and makes update failures report honestly instead of always claiming success.

v1.26.2 August 21, 2026

v1.26.2 — **Hotfix. Update immediately if you are on 1.26.1.**

Hotfix. Update immediately if you are on 1.26.1.

Login returned HTTP 500 on 1.26.1

1.26.1 broke the panel on every install. Auth::clientIp() — added in that release to record the real client IP for logging and fail2ban — called itself as its own final fallback instead of reading REMOTE_ADDR. Infinite recursion, stack exhaustion, HTTP 500.

It only triggered when a request arrived with no CF-Connecting-IP and no X-Forwarded-For header, which is every direct request and every request after login. Requests arriving through a Cloudflare Tunnel took an earlier branch and returned before reaching it, which is why it was not caught before release.

One line. No configuration change, no data affected, nothing to undo.

If you are stuck on 1.26.1

The nightly auto-update will apply this. To fix it now from a root shell:

inetp panel_update

If the panel is unreachable and you would rather patch by hand, in /var/www/inetpanel/TiCore/Auth.php change the line inside clientIp():

$ra = self::clientIp();                    // broken
$ra = $_SERVER['REMOTE_ADDR'] ?? '';       // correct

then systemctl reload php8.5-fpm.

Apologies

This should not have shipped. The security fixes in 1.26.1 were tested, but this helper's *output* was verified without ever calling it on the one code path that recursed. 1.26.1 remains the release that fixes the privilege escalation — please update to 1.26.2 rather than rolling back to 1.26.0.

v1.26.1 August 20, 2026

v1.26.1 — **Security release. Update promptly** — one finding allows

Security release. Update promptly — one finding allows a hosting tenant to gain root on the host.

Local privilege escalation via .htaccess/.htpasswd (GHSA-mjmx-xpqq-p2h8)

Severity: High (CVSS 8.8). An authenticated hosting tenant — the lowest privilege the panel issues — could obtain arbitrary root file read, write and chown on the host.

The account portal's .htaccess/.htpasswd manager validated the *directory* of the target path but appended the filename afterwards, unchecked. Because a tenant controls their own document root over SFTP, replacing .htaccess with a symlink made the panel's root cp/cat/chown follow it — reading /etc/shadow, writing /etc/cron.d/, or taking ownership of any root-owned file.

All such access now goes through a single root helper. Callers pass an account and a *relative* directory, never a path; the helper re-derives the document root from the panel database, re-checks domain ownership, and opens the file with O_NOFOLLOW so the kernel refuses symlinks. Eight broad sudo grants over /home/* were removed and replaced with one narrow grant.

Reported privately. No action is required beyond updating — the fix applies automatically.

phpMyAdmin sign-on credentials were world-readable

The temporary sign-on token file held a plaintext database password — the MySQL root password on the admin route — at mode 0644. It is unlinked when phpMyAdmin picks it up, so the exposure was abandoned hand-offs, which lingered indefinitely and were readable by any local user, including every hosting tenant. Now created 0600 before any content is written, with stale tokens swept.

Hosted sites saw the wrong client IP for every request

Behind Cloudflare Tunnel, cloudflared connects over loopback, so REMOTE_ADDR was 127.0.0.1 for every request on every hosted site. Consequences:

  • per-IP rate limiting in any hosted app was one global bucket
  • access logs could not be used for abuse investigation
  • the panel's own fail2ban jail recorded failed logins as loopback — which jail.local ignores — so it logged every brute-force attempt and could ban none
  • apps compensating by parsing X-Forwarded-For got it wrong, because Cloudflare *appends* to that header: the left-most entry is attacker-controlled
inetp cf_remoteip now configures mod_remoteip with Cloudflare's published ranges, refreshed weekly. Hosted applications need no changes — REMOTE_ADDR is simply correct.

Sessions were shared across every domain in an account

Session storage was scoped per *account* rather than per domain, so a session and its CSRF token minted on one domain were valid on every other domain that account owns. For anyone using the multi-domain feature, a staging site or a legacy app with an XSS became a foothold against their more valuable domains.

Sessions are now scoped per domain with a per-domain cookie name, and hosted sites get HttpOnly, Secure, SameSite=Lax and strict session-id mode — none of which PHP sets by default.

> This logs users out once. The change applies to a domain when its PHP-FPM pool is next written (adding a domain, or inetp rebuild_pools). Existing sessions for that domain end at that point. Nothing else is affected.

PHP session files were never deleted

The panel's per-pool session path is invisible to Debian's session cleaner, which reads only the system php.ini. PHP's own collector is disabled on Debian by design. Nothing removed them: one account reached roughly 4.2 million files, with the directory entry alone at 281 MB — a cost paid on every session lookup.

inetp session_reaper now runs hourly, honours session.gc_maxlifetime, and works in bounded batches with nice/ionice, because an unbounded delete over millions of files can stall a container.

Run inetp session_reaper --dry-run after updating to see what has accumulated. Note that ext4 does not shrink a directory entry once grown — reclaiming that space requires recreating the directory, which the dry run flags.

Per-site logs were never rotated

Nothing in /etc/logrotate.d/ matched /home, so hosted sites' Apache logs grew without bound — 218 MB and 106 MB access logs were found unrotated since April — at mode 0644. Access logs record full query strings, so any application placing a token in a URL leaked it into a world-readable file.

Ships /etc/logrotate.d/inetpanel-sites (weekly, 8 kept, copytruncate, mode 0640), and tightens existing world-readable site logs on update.

Upgrading

inetp panel_update, or wait for the nightly auto-update. Everything except the session change is transparent.

With thanks to the security researcher who reported the privilege escalation privately, and to the operator whose fleet audit surfaced the rest.

v1.26.0 August 17, 2026

v1.26.0 — Adds Debian 13 (Trixie) support and serves the setup wizard

Adds Debian 13 (Trixie) support and serves the setup wizard over HTTPS.

Debian 13

Both Debian 12 (Bookworm) and Debian 13 (Trixie) are supported. Debian 12 left regular support on 2026-07-11 but stays on LTS until June 2028, so existing servers do not need to migrate.

Verified by a full clean install on Debian 13: install → create account → DNS and certificate → delete account.

What had to change:

  • dnsutils no longer exists on Trixie. It was a transitional package. The installer now uses bind9-dnsutils, which exists on both releases. This was the first hard failure — the installer died before installing anything.
  • The PHP repository was pinned to bookworm. That suite still resolves, so the failure came later and confusingly: sury's bookworm build depends on libssl3 and Trixie only ships libssl3t64.
  • cloudflared stays on the bookworm suite deliberately. Cloudflare publishes no Trixie suite, and the bookworm package is a static Go binary with no dependencies, so it runs correctly on Trixie. There is now also a direct .deb fallback so a repository outage cannot abort an install part-way.
  • PHP version pinning is built dynamically. phpMyAdmin depends on php-cli/php-mysql, which resolve to a full PHP 8.4 stack on Trixie. The old pin only blocked 8.6 and above, so two complete PHP stacks would end up installed side by side.

Setup wizard is now HTTPS

The wizard collects the admin password and, a few steps later, your Cloudflare API key. Both previously went over plain HTTP, and there was no HTTPS option until panel_ssl.sh was run by hand — which is after those credentials have been sent.

The installer now generates a self-signed certificate, serves the panel over TLS on 443, and redirects HTTP. Your browser will warn once; that is expected, and the closing message explains it. Replace it with a trusted certificate at any time:

inetp panel_ssl <hostname>

Fixed

Certificates could silently be self-signed on Debian 13. Trixie ships certbot 4.0, and certbot-dns-cloudflare 4.x removed support for the Cloudflare Global API Key. Issuance would fail, the panel would fall back to a self-signed certificate, and report success. It now uses a scoped API token (Settings → Cloudflare), refuses rather than degrading, and verifies the certificate issuer before calling a domain done.

inetp db_repair did nothing on Debian 13. MariaDB 11.x ships most mysql* compatibility symlinks but not mysqlcheck, and the error was discarded — so it reported zero tables checked and exited successfully.

Changing the SSH port could lock you out. Under systemd socket activation the listening port comes from ssh.socket, not sshd_config, but the check used sshd -T, which reads the config. It now verifies the actual listener and keeps the socket unit in step.

WireGuard setup failed where iptables was absent. It is not a dependency of wireguard-tools, and firewalld 2.x no longer pulls it in. The NAT rules now prefer nft.

A newly added domain could return 503. An API request omitting the PHP version reloaded a PHP-FPM service that did not exist, so the new pool's socket was never created. Related: a domain set to inherit the panel default produced an invalid service name, and its FPM reload silently never happened.

Also

install_LAMP.sh is now tracked in the repository, so it is covered by the same lint and secret-scanning as every other script.

Upgrading

Existing Debian 12 servers are unaffected and need no action. To move the panel to HTTPS on an existing install, run inetp panel_ssl <hostname>.

v1.25.0 August 6, 2026

v1.25.0 — Account deletion is now atomic, resumable and verifiable. Pr

Account deletion is now atomic, resumable and verifiable. Previously a deletion could fail half-way through, report success anyway, and leave resources behind that the panel had already forgotten about — four accounts on one server were each found in a different state of partial deletion.

The root cause

delete_account.sh, delete_user.sh and remove_domain.sh all ended on an echo, so they exited 0 no matter how many steps had failed. The panel gates its database cleanup on that exit code, so it deleted the hosting_users row — the only record naming what still needed removing — and the leftover became invisible in the UI.

Every step in those scripts now verifies its own post-condition and the script exits non-zero if any of them failed. Re-running a deletion against a partially deleted account completes the job rather than refusing to start.

Fixed

Databases were never dropped when an account was deleted. delete_user.sh dropped none at all, and remove_domain.sh only dropped a database named after the domain — so any database created through the account portal survived every deletion.

User crontabs outlived the account. userdel -r removes /var/spool/cron/crontabs/<user>, but only while the passwd entry still exists, and one code path removed the home directory without going through userdel. The file is keyed by name but owned by the numeric uid, and that uid returns to the free pool — so a future account could inherit, and read, a dead account's scheduled jobs. Crontabs are now removed before the system user, and verified.

remove_domain.sh could drop the wrong databases. It matched LIKE '<user>_<domain>%', where _ is a single-character wildcard in MySQL and the trailing % matched anything sharing the prefix. Removing example.com could take example_com_dev and example_com_backup with it. It now matches the exact name.

Deleting an account whose system user was already gone was impossible. delete_account.sh exited immediately on "System user does not exist" — precisely the state a failed deletion leaves behind, so the panel could not clean up after itself.

FTP and portal access could survive deletion. The /etc/vsftpd.userlist cleanup silently did nothing when the account being removed was the only entry in the file. That file also authorizes hosting-portal login, so the entry was a live credential, not cosmetic.

Ports were burned permanently. The Listen line was only removed if the vhost file still existed, so a vhost that had already gone left its port allocated forever.

Accounts silently stopped being backed up. A hosting account whose home directory was missing was skipped with no message, no log entry and no counter — so it looked healthy in the UI while nothing was being saved. Two accounts were lost this way, having aged past the retention window while orphaned. Backups also now check tar's exit status instead of reporting success unconditionally, and the retention sweep no longer descends into subdirectories.

Added

inetp audit_orphans — reconciles hosting accounts against /etc/passwd, /home, the FTP allowlist, crontabs, databases, MariaDB users, Apache vhosts, PHP-FPM pools, certificates and ports, reporting divergence in both directions:

sudo inetp audit_orphans              # whole server
sudo inetp audit_orphans --user bob   # one account

It is read-only and has no --fix mode by design. Exit codes: 0 clean, 1 something was found, 2 could not check.

A last-chance backup before deletion. Written to /backup/deleted/, which the retention sweep cannot reach. If it fails, the deletion aborts with nothing destroyed.

Deletion intent is recorded before anything is destroyed, under /var/lib/inetpanel/deleting/, and cleared only once every step has confirmed success. An interrupted deletion leaves a findable record instead of an invisible orphan, and the panel refuses to create a new account with a name whose deletion never finished.

A MariaDB plugin-version check (#22). When mariadb-plugin-provider-* packages fall out of step with mariadb-server, the daemon refuses to start on its next restart. The audit now reports this before something restarts MariaDB, rather than after.

Upgrading

Nothing runs automatically on update. After upgrading, sudo inetp audit_orphans is worth running once — it will tell you whether any previous deletion left something behind.

v1.24.5 July 25, 2026

v1.24.5 — Reliability release. Fixes the orphaned-vhost bug behind #17

Reliability release. Fixes the orphaned-vhost bug behind #17, where a deleted domain could leave Apache unable to start and take every site on the server down.

Fixed

Orphaned Apache vhosts (#17). Deleting an account enumerated its domains by scanning the home directory. If that directory was already gone, the vhost survived pointing at a DocumentRoot and log directory that no longer existed. Apache kept serving on its loaded config, so nothing looked wrong — until the next restart (an apt upgrade, a reboot), at which point a missing log directory is fatal and Apache refused to start. Every site on the box then returned 502. Domains are now resolved from the panel database and vhost ownership as well as the filesystem.

Apache config is validated before every reload. remove_domain.sh ran reload || restart — backwards, because a failed reload leaves the working config serving, but the restart that followed took the whole server down. Both add_domain.sh and remove_domain.sh now run apache2ctl configtest first and never escalate to a restart.

add_domain no longer overwrites a live site's index.php. Re-adding an existing domain to regenerate its vhost copied the welcome placeholder over the real homepage. The placeholder is now only written into an empty web root.

inetp status printed garbage and always claimed an update was available. The version lookup matched every line mentioning APP_VERSION, including doc comments, producing a multi-line blob that never compared equal. It also falls back to ASCII on non-UTF-8 consoles, where the status dots rendered as ?.

inetp dns_check example.com now works. It previously required --domain and rejected a bare domain.

The SSH port survives an openssh-server upgrade. The port was written into /etc/ssh/sshd_config, which is ucf-managed on Debian — accepting the maintainer's version on the upgrade prompt silently reverted it and locked you out on the next reconnect. It now lives in /etc/ssh/sshd_config.d/99-inetpanel.conf, and the change is verified to have taken effect before being reported as successful.

Added

inetp rebuild_vhosts — regenerates missing Apache vhosts from the panel database and reports orphaned ones. Run inetp rebuild_vhosts --check for a read-only report; it will tell you whether a broken vhost is waiting to take your server down on the next reboot.

Upgrading

Nothing in this release runs automatically. All of the above execute only on explicit action, so the update itself is a straight file replacement.

If you have ever deleted a domain or an account, sudo inetp rebuild_vhosts --check is worth running once after updating.

v1.24.4 June 9, 2026

v1.24.4 — CRITICAL: php-fpm ProtectSystem fix (account creation broken on updated servers)

⚠️ CRITICAL — please read, and enable automatic backups

This release fixes a system-wide bug that can silently break account creation, domain setup, and SSL on servers that were previously working perfectly. If you run iNetPanel, update and read this.

► Enable automatic backups now

Settings → Backups → enable daily automatic backups, and take a manual backup before updating. The failures in this bug are silent — operations report success while doing nothing — so a known-good backup is your safety net.

The bug — full details

  • What: php-fpm's systemd unit ships with ProtectSystem=full, which mounts /etc read-only for every process php-fpm spawns. The panel makes privileged system changes — useradd, Apache vhosts, PHP-FPM pools, vsftpd entries, Let's Encrypt certs — via root helpers launched by php-fpm, and sudo does not escape php-fpm's mount namespace, so all of those /etc writes fail.
  • Symptoms: creating an account fails with useradd: cannot lock /etc/passwd; try again later or Read-only file system; adding domains / issuing SSL can fail the same way. Until this release the scripts reported success anyway, leaving "phantom" accounts (a hosting_users row with no Linux user) that then block re-creating that username.
  • Why it appeared out of nowhere: ProtectSystem=full is part of the stock php-fpm unit; a routine PHP package update in the past few months enabled it on servers that were fine before. No panel change was involved — a working system simply stops being able to create accounts. It looks like a storage/permission problem but isn't — it happens identically on ext4 and ZFS.
  • The fix

    A php-fpm drop-in sets ProtectSystem=true (keeps /usr and /boot read-only, allows /etc). New installs get it from the installer before php-fpm first starts; existing installs get it on update.

    ⚠️ Applying it on an already-affected server

    Most servers need no action — the nightly auto-update (runs as root via cron, outside the sandbox) applies this fix automatically. Just make sure auto-update is enabled under Settings → Updates.

    To apply it immediately, or if auto-update is off, run once from a root shell:

    inetp panel_update      # or: php /var/www/inetpanel/scripts/panel_update.php

    The web "Update Now" button cannot apply this particular fix — it runs *inside* php-fpm's read-only sandbox. After the fix applies, php-fpm stops sandboxing /etc and everything (including "Update Now") works normally again. If a username is stuck as a phantom account, delete its hosting_users row and re-create it.

    ---

    Also in v1.24.4

  • create_user.sh now fails loudly when useradd fails (no more phantom accounts) — this is what finally surfaced the bug above.
Full changelog: https://github.com/tuxxin/iNetPanel/compare/v1.24.3...v1.24.4
v1.24.3 June 6, 2026

v1.24.3 — HTTP/1.1 origin hardening + port-collision & multi-PHP fixes

iNetPanel v1.24.3

Bug-fix release hardening multi-tenant isolation and fixing per-domain PHP switching.

Fixed

  • Cross-vhost content contamination. The Cloudflare→Apache origin hop is now pinned to HTTP/1.1 via a managed conf-available/inetpanel-origin.conf (Protocols http/1.1), applied idempotently on update (configtest + safe reload, auto-revert on failure) and by the installer for new installs. This prevents HTTP/2 connection coalescing from ever serving one domain's content (e.g. sitemap.xml) under another. Visitors keep HTTP/2/3 from Cloudflare's edge — only the origin hop changes.
  • Port-collision isolation. add_domain now allocates each vhost port atomically (flock) and scans real usage (ports file + live vhosts), refusing to co-locate two domains on one port. Backup restore re-derives unique, free ports server-side instead of trusting client-submitted values.
  • Per-domain PHP version switch. Switching a domain's PHP version now runs through a root multiphp_manage --action set_domain action using the correct {user}_{domain} pool naming, and validates the domain first.
  • Upgrade

  • Existing installs apply the hardening and fixes automatically on update; new installs include them in the installer.
Full changelog: https://github.com/tuxxin/iNetPanel/compare/v1.24.2...v1.24.3
v1.24.2 April 21, 2026

v1.24.2 — Installer cascade fix (#14, #15) + per-domain disk cache

Bug Fixes

  • Installer fails on fresh Debian 12 (closes #15). The installer's exec_cmd helper ran commands in a backgrounded subshell that didn't have /usr/sbin in PATH, so a2enmod failed. Added a global PATH export near the top of install_LAMP.sh and switched a2enmod/a2ensite/a2dissite/a2disconf to absolute /usr/sbin/ paths.
  • Unusable MariaDB / phpMyAdmin on fresh install (closes #14). Same root cause as #15: when a2enmod exited early, the installer skipped the sudoers file, phpMyAdmin storage DB, and panel deploy. Without the sudoers rule www-data couldn't read the MariaDB root password — producing *"Access denied for user 'root'@'localhost' (using password: NO)"*. Fix #15 resolves the cascade.
  • Same disk usage shown on every domain for multi-domain users. accountDisk() was summing /home/<user> (the whole home dir) and the user's DB total on every row. Now a new disk_cache SQLite table tracks per-domain file usage, disk_cache_user tracks the per-user DB total, and the Accounts page shows each domain's actual files size plus a single *Total:* badge per user (files across all their domains + MariaDB total).
  • Performance

  • /admin/accounts load time. With 30+ domains and 100 GB+ data the page hung for seconds while du -sb ran live for every user. New scripts/system/disk_cache_scan.sh populates the SQLite cache every 10 minutes via cron, and fires immediately after add/remove/create/delete via background inetp calls. The API now reads cached rows — O(ms) regardless of dataset size.
  • Docs / Packaging

  • README install command now pre-installs curl for fresh Debian 12 installs (apt-get install -y curl && bash <(curl -s …/latest)).
  • build_release.sh now also generates latest-beta alongside latest — same installer, just with the download URL swapped to the main-branch zipball so beta testers pull the latest code instead of the tagged release zip.
v1.24.1 April 18, 2026

v1.24.1 — Dashboard speed + www ServerAlias hotfix

Performance

  • Dashboard load time/api/accounts?action=list was running du -sb + inetp db_size for every hosting user on every dashboard load (5+ seconds with 31 domains), even though the dashboard only displays 6 rows and doesn't use disk usage. Added ?limit=N and ?skip_disk=1 query params; dashboard now requests limit=6&skip_disk=1. Accounts page unchanged — still shows disk usage.
  • Bug Fixes

  • Apache vhost missing www ServerAlias — Apex domains (example.com) had the DNS www CNAME auto-created by the Cloudflare tunnel logic, but the Apache vhost only declared ServerName ${DOMAIN}, so www.{domain} requests fell through to the default vhost. add_domain.sh and restore_account.sh now add ServerAlias www.${DOMAIN} for apex domains (dot-count heuristic: exactly 1 dot, not already prefixed with www.).
v1.24 April 4, 2026

v1.24 — Backup Restore with CF Migration Support

New Features

  • Backup Restore — Full account restore from backup archives with 4-step wizard: Upload (web with progress bar, FTP, SSH/SCP) → Review (username, domains, ports, databases) → Cloudflare routing check with override → Execute with progress and credential summary.
  • Domain availability checkerinetp check_domains uses RDAP (free, no API key) for single, bulk, and auto-generated variant checks.
  • CF tunnel migration — Restore override adds route to new tunnel and removes from old tunnel automatically. Searches all account tunnels for domain conflicts, not just the current server's tunnel.
  • Bug Fixes

  • Tunnel creation fails on fresh install — Removed invalid tunnel_secret from API-managed tunnel creation.
  • Beta channel update detection — Was always showing "up to date"; now compares commit SHAs.
  • Beta version string stacking — Stripped -beta.x-beta.y chains to just base + latest hash.
  • Domain deletion "Connection error" — FPM reload in remove_domain.sh killed panel worker; deferred to after response.
  • Restore connection reset at 95% — Same FPM reload issue in restore_account.sh.
  • Restore FTP login — Fixed nologin shell, sudo permissions, password (now copies root's shadow hash).
  • Restore parse on large backups — Replaced full tar -tzf buffering with streaming grep pipes (constant memory).
  • CF CNAME safetyremoveTunnelHostname() now only deletes CNAME if it points to the specific tunnel being cleaned up, preventing breakage when domains are migrated between servers.
  • CF domain conflict detectioncheck_domain and add_domain now search all account tunnels, not just the current server's.
v1.23.3 April 4, 2026

v1.23.3 — Fix tunnel creation, beta update detection, add domain checker

Bug Fixes

  • Tunnel creation fails on fresh install — Removed tunnel_secret from createTunnel(). API-managed tunnels (config_src: cloudflare) reject client-side secrets; Cloudflare's Rust serde threw a JSON deserialize error. Token is already retrieved separately via getTunnelToken().
  • Beta channel update detection brokenupdate_check.php and settings.php check_updates action only queried GitHub releases API, never checking main branch commits. Beta channel now compares latest commit SHA against installed SHA to properly detect available updates.
  • New Features

  • Domain availability checker — Added check_domains.sh (inetp check_domains). Uses RDAP (free, no API key). Supports single domain, bulk file (-f), and auto-generated variant checks (-g keyword).
v1.23.2 March 26, 2026

Hotfix

  • Installer fails to download panel sourceinstall_LAMP.sh was pointing at inetpanel.tuxxin.com/inetpanel-latest.zip (404). The zip is a GitHub release asset, not hosted on the website. Fixed URL to github.com/tuxxin/iNetPanel/releases/latest/download/inetpanel-latest.zip.

Install / Update

bash <(curl -s https://inetpanel.tuxxin.com/latest)
v1.23.1 March 26, 2026

Hotfix

  • Multi-PHP install fails silently on fresh installs — The /var/www/inetpanel/storage/ directory wasn't created during deployment, causing status files and logs to never be written. The API now creates the directory if missing and returns a clear error if it can't.

Install / Update

bash <(curl -s https://inetpanel.tuxxin.com/latest)
Existing installs: run inetp panel_update from the admin panel or CLI.
v1.23 March 26, 2026

New: Release Channels (Stable vs Beta)

  • Installer now uses release tarballs instead of git clone — the repo (main branch) is now beta code, while GitHub Releases are stable.
  • - inetpanel.tuxxin.com/latest — stable installer (downloads release zip) - inetpanel.tuxxin.com/latest-beta — beta installer (clones main branch)
  • Settings → Updates → Release Channel — toggle between stable and beta without reinstalling. Stable pulls from tagged releases, beta pulls the latest commit from main.
  • Fixes

  • Multi-PHP install broken on all environmentssystemd-run --scope fails silently in LXC/Proxmox containers with no fallback, leaving the status file stuck at "running" forever. Added direct background execution fallback when systemd-run fails. Simplified the API exec() call and set a 3-minute timeout.

Install / Update

bash <(curl -s https://inetpanel.tuxxin.com/latest)
Existing installs: run inetp panel_update from the admin panel or CLI.
v1.22.3 March 26, 2026

Fixes

  • Dashboard disk stats inflateddisk_free_space() excludes ext4 reserved blocks (~5%), making "used" appear ~10 GB higher than reality. Now uses df output for accurate numbers matching what the OS reports.
  • PHP 5.6 install timeout — Polling timeout increased from 120s to 360s. Older PHP versions take longer to install via apt.
  • SSH port fix for LXC containers — Previous fix only checked is-active on ssh.socket, which misses cases where the socket is enabled but not yet active (common in LXC). Now checks list-unit-files and masks the socket unit to prevent reactivation.

Install / Update

curl -o latest https://inetpanel.tuxxin.com/latest && bash latest
Existing installs: run inetp panel_update from the admin panel or CLI.
v1.22.2 March 26, 2026

Fixes

  • SSH port ignored on Debian 12 — Installer now disables ssh.socket (systemd socket activation hardcodes port 22, overriding sshd_config Port 1022)
  • SSL cert reissue blocked after self-signed fallback — Cleans up non-LE certs from /etc/letsencrypt/live/ before certbot retry
  • CertBot DNS propagation timeout — Added --dns-cloudflare-propagation-seconds 30 (default 10s was too short)
  • Dashboard graph empty on fresh installinetpanel_stats was missing from manage_cron.sh allowed list, so the stats collector cron was never created
  • New

  • inetp optimize_server — Auto-tunes Apache2 mpm_event and MariaDB InnoDB/buffers based on detected RAM, CPU cores, disk type (SSD/HDD), and hosted domain count. Dry-run by default, --apply to write changes with automatic config backups.

Install / Update

curl -o latest https://inetpanel.tuxxin.com/latest && bash latest
Existing installs: run inetp panel_update from the admin panel or CLI.
v1.22.1 March 23, 2026

Logo rebrand & dark mode fixes (v1.22.1)

New branding

  • New logo — redesigned iNetPanel logo (PNG with transparency), replaces old .webp
  • Favicon — new Logo-Icon.webp favicon added to admin and account portals
  • Logo automatically inverts to white in dark mode via CSS filter
  • Account portal logo resized to 28px for cleaner navigation fit
  • Dark mode — root-level fix

    The core issue with dark mode was that Bootstrap 5.3 uses internal CSS variables (--bs-body-color, --bs-table-color, --bs-border-color, etc.) that our custom [data-theme="dark"] wasn't overriding. This caused all Bootstrap components to inherit dark text colors regardless of theme.

    Fix: Override all Bootstrap 5.3 internal CSS variables at the [data-theme="dark"] root level. This makes every Bootstrap component — tables, cards, headings, links, borders — automatically inherit correct dark mode colors without needing per-component overrides.

    Specific fixes

  • Table headers.table-light <thead> elements now display light text (previously black on dark background)
  • Nav tabs — dark mode styling for tabbed interfaces (multi-php, firewall pages)
  • Bootstrap 5.3 subtle utilitiesbg-success-subtle, bg-primary-subtle, bg-warning-subtle, bg-danger-subtle with proper dark variants
  • Close button — filter inversion for visibility on dark backgrounds
  • Warning badges — retain dark text for contrast on yellow background
  • Table borders — consistent border color in dark mode

Pages fixed

Accounts, DNS, Email, SSL, Firewall, Multi-PHP, Services, and all other pages that use Bootstrap tables, cards, or badges.

Closes #10

v1.22 March 22, 2026

Dark mode audit & fixes (v1.22)

Addresses #10 — dark mode inconsistencies and unreadable text.

CSS additions (58 new dark mode rules)

  • Badgesbg-light, text-dark, bg-secondary now theme-aware
  • Alerts — info, success, warning, danger with proper dark backgrounds and contrast
  • Pre/code blocks — proper dark background and text colors
  • Buttonsoutline-dark, outline-secondary, btn-light variants
  • Components — breadcrumbs, nav pills, pagination, input groups, progress bars, accordion, offcanvas, toast
  • Logo — brightness inversion filter for dark sidebar backgrounds
  • Hardcoded color fixes

  • Dashboard chart grid/tick colors now adapt to theme
  • QR code colors invert in dark mode (light modules on dark background)
  • Removed hardcoded #e8f4f8 alert background in settings
  • WireGuard code block uses theme-aware bg-light instead of bg-white
Closes #10
v1.21.9 March 22, 2026

Fix PHP package extension install/remove (v1.21.9)

Same root cause as v1.21.8dpkg triggers restart php-fpm during apt-get install/remove of individual extensions (e.g. php8.5-uploadprogress), killing the PHP worker mid-request.

Changes

  • Extension install/remove now runs inside systemd-run --scope to escape the FPM cgroup
  • Frontend uses async status polling with progress modal (matches multi-php pattern)
  • Modal only shown after API confirms operation started (prevents stuck modal on errors)
  • Added pkg_status API endpoint for polling extension install/remove progress
v1.21.8 March 22, 2026

Fix Multi-PHP install/remove (v1.21.8)

Root cause: dpkg triggers restart php-fpm during package install/remove, which kills all processes in the FPM cgroup — including the background apt script launched from PHP exec(). nohup/setsid don't help because systemd kills by cgroup, not process group.

Changes

  • Script re-launches itself via systemd-run --scope to escape the FPM cgroup before running apt
  • Fixed status file path mismatch between API and backend script
  • Fixed concurrent operation guard falling through to exec() instead of breaking the switch
  • Modal now only shown after API confirms success (prevents stuck modal on errors)
  • Status file removed immediately after verification, before slow post-cleanup steps
  • Moved apt logic from inline PHP to dedicated multiphp_manage.sh bash script
v1.21.7 March 22, 2026

v1.21.7 — Fix Multi-PHP: detached apt execution

Bug Fix

Multi-PHP install/remove silently fails

apt-get ran inside the PHP-FPM worker process. dpkg post-install hooks restart php-fpm, which kills the worker mid-execution — apt never completes, status file stuck at 'running', modal spins forever.

Fix: All apt operations now run in a detached bash script (sudo bash script.sh &) that survives FPM restarts. The script handles the full lifecycle: dpkg configure, apt install/purge, FPM enable/start, upload limits, module reinstall, and cleanup. Status tracking via temp file allows the polling UI to detect success (file removed) or failure (file contains 'error').

Files Changed

  • api/multiphp.php — Detached bash wrapper for apt operations
  • src/multiphp.php — Improved polling with seenRunning state tracking
  • TiCore/Version.php — Version bump
v1.21.6 March 22, 2026

v1.21.6 — Fix Multi-PHP status tracking

Bug Fix

Multi-PHP status file stuck at 'running'

The v1.21.5 status file mechanism never transitioned from 'running' to 'done' because dpkg post-install hooks triggered an FPM restart that killed the PHP worker before it could update the file. The polling modal would spin until timeout.

Fix: Status file is deleted immediately after apt succeeds, before any FPM operations. On apt failure, error is written and execution stops immediately.

Files Changed

  • api/multiphp.php — Reorder status file cleanup before FPM operations
  • TiCore/Version.php — Version bump
v1.21.5 March 22, 2026

v1.21.5 — Fix Multi-PHP background failure detection

Bug Fix

Multi-PHP install/remove modal spins forever on failure

When apt-get failed silently in the background (e.g. package not available, dpkg lock), the polling modal would spin indefinitely since the version state never changed.

Fix: Background apt operations now write status to a temp file. The polling list action reports this status, allowing the UI to detect errors and show them immediately instead of spinning for 3 minutes.

Files Changed

  • api/multiphp.php — Status file tracking for background apt operations
  • src/multiphp.php — Poll checks for background error status
  • TiCore/Version.php — Version bump
v1.21.4 March 22, 2026

v1.21.4 — Fix Multi-PHP removal & FPM pool cleanup

Bug Fixes

Multi-PHP removal causes 500 error

Removing a PHP version via the admin UI triggered an Internal Server Error because apt-get purge restarted the panel's own PHP-FPM process mid-request.

Fix: apt-get now runs after fastcgi_finish_request() so the browser receives a success response before the package removal begins. The UI polls until the version is fully removed before closing the progress modal.

Multi-PHP removal leaves packages behind

The purge command only removed specific packages (fpm, cli, common, etc.) but missed sub-packages like bz2, gd, intl — leaving the binary installed.

Fix: Uses wildcard php{ver}-* purge + apt-get autoremove to fully remove all packages for that version.

FPM pools not cleaned up on domain/account removal

Removing a domain or account from the admin UI deleted the pool config file but never reloaded PHP-FPM, leaving orphaned worker processes running.

Fix: Both the shell script and API now reload FPM after pool removal.

Database creation SQL syntax error

Creating a database from the client portal failed with "Unknown command" due to escaped backticks in the SQL statement.

New Features

inetp rebuild_pools

New CLI command to regenerate all missing PHP-FPM pool configs from the panel database. Useful for recovery after pool files are accidentally deleted.

Dashboard "Accounts & Domains"

The dashboard card now shows both account count and domain count (e.g. "3 / 5").

Files Changed (9 files)

TiCore/Version.php, api/account.php, api/accounts.php, api/multiphp.php, scripts/system/inetp, scripts/system/rebuild_pools.sh, scripts/system/remove_domain.sh, src/dashboard.php, src/multiphp.php
v1.21.3 March 19, 2026

v1.21.3 — Fix backup toggle and retention

Bug Fixes

Backup toggle not disabling backups

Toggling backups off in admin settings had no effect — the cron was always written with the backup command, and the script never checked the backup_enabled setting. Fresh installs saw system config backups appearing even with backups toggled off.

Fix:

  • api/settings.php: Checks backup_enabled before writing backup cron; writes a disabled comment when off

  • backup_accounts.sh: Exits early if backup_enabled = 0 (safety net); --single mode still works for domain removal
  • Backup retention not cleaning old files

    The retention cleanup never removed old backups because MTIME_DAYS was calculated inside the loop body but used in the find command's process substitution (which runs before the loop).

    Fix: Moved MTIME_DAYS calculation before the find command.

    Files Changed

  • api/settings.php — Backup toggle cron logic
  • scripts/system/backup_accounts.sh — Early exit guard + retention fix
  • TiCore/Version.php — Version bump
v1.21.2 March 19, 2026

v1.21.2 — Fix database creation, updated README

Bug Fix

Database creation from client portal

CREATE DATABASE used escapeshellarg() which wraps the name in single quotes — MySQL requires backticks for identifiers. All database creation attempts from /user/dashboard#database failed with ERROR 1064. Now uses backtick-wrapped identifiers with regex sanitization.

Updated

  • README rewritten — streamlined with home hosting focus, technical stack details, directory layout, and links to [inetpanel.tuxxin.com](https://inetpanel.tuxxin.com) for full documentation
  • Files Changed

  • api/account.php — Fix CREATE DATABASE SQL syntax
  • README.md — Rewrite
  • TiCore/Version.php — Version bump

Release notes are fetched from github.com/tuxxin/iNetPanel/releases and cached for 6 hours.

Share: 𝕏 Twitter Facebook LinkedIn