Install & upgrade troubleshooting
What to do when the installer stops, an upgrade does not take, or the service will not come back up. Search the exact error text — every entry here is a failure path that really exists in the installer or that a release actually changed.
Start here
The installer stopped. Where do I look?
The installer prints a red ✗ line naming what failed. That line
is the thing to search for on this page.
If the install finished but the service is unhealthy, the service's own log is the next place:
sudo journalctl -u jt-proxense -n 50 --no-pager
Check which version is actually installed:
jt-proxense versionInstall fails
This installer must run as root
The one-liner needs sudo. It creates a system user, writes a
systemd unit and installs into /opt.
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
Note the sudo goes on bash, not on curl.
No supported package manager (apt / dnf / yum / pacman / zypper) found
The installer resolves system packages through one of those five. On a distribution it does not recognise, install the prerequisites by hand and then re-run it — it skips what is already present:
git, python3 (3.10+), python3-pip, python3-venv, systemdPython 3.10+ required (found ...)
Most common on RHEL / Rocky / AlmaLinux 8, which ship Python 3.6 as
python3:
sudo dnf install python3.11 python3.11-pip
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
The installer picks the newest interpreter it finds and points the systemd unit at it, so you do not need to change the system default.
/opt/jt-proxense is non-empty but not a git checkout. Refusing to overwrite.
Something is already in the target directory that the installer did not put there — an old manual copy, or an extracted archive. It refuses rather than deleting your files.
Move it aside and re-run:
sudo mv /opt/jt-proxense /opt/jt-proxense.old
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
Or install somewhere else:
curl -fsSL … | sudo JT_PROXENSE_INSTALL_DIR=/opt/jtp bash
Your data is not in that directory — users, audit log and
settings live in /var/lib/jt-proxense and /etc/jt-proxense,
and survive this.
Smoke test failed — a runtime module did not import
Python dependencies installed but one of them will not load. See the exact module name in the line above the failure, then:
sudo python3 -m pip install --upgrade -r /opt/jt-proxense/requirements.txt
sudo systemctl restart jt-proxense
On Debian 12+ / Ubuntu 24.04 the base environment is marked externally managed
(PEP 668). The installer detects this and adds
--break-system-packages automatically; if you are installing by hand
you need the same flag.
git clone or curl fails / the host cannot reach GitHub
The installer needs github.com and
raw.githubusercontent.com. Behind a proxy, export it for the
whole command — a proxy set only for your shell will not reach the
sudo environment:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh \
| sudo https_proxy=http://proxy:3128 bash
For a fully air-gapped host, clone the repository elsewhere, copy it to
/opt/jt-proxense, and run install.sh from inside it.
Upgrade fails
How do I upgrade? Is git pull enough?
Re-run the same one-liner. It is idempotent and it is the upgrade path:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
A bare git pull is not enough. It fetches code but
does not reinstall the systemd unit, refresh Python dependencies, or apply the file
ownership and permission repairs — so you end up running new code under an old
unit. If you have already done that, just run the one-liner afterwards; it repairs
the rest.
Which version does the installer put on? Can I pin one?
Since v1.1.0 it installs the newest release tag, not the tip
of main — so the same command run on two different days gives the same
code, and there is something to roll back to.
Pin a specific release, or deliberately follow the development branch:
# a specific release
curl -fsSL … | sudo JT_PROXENSE_REF=v1.1.0 bash
# the development branch
curl -fsSL … | sudo JT_PROXENSE_REF=main bashYour local changes would be overwritten / dubious ownership
The installer resets the checkout to the release it is installing, so edits
made inside /opt/jt-proxense are discarded by design. Keep changes in
config.yaml, which is never overwritten.
If git refuses with detected dubious ownership, it is because the checkout is root-owned (deliberate since v1.1.0 — the service must not be able to modify its own code). Run git as root, or let the installer do it:
sudo git -C /opt/jt-proxense statusIt says it upgraded, but the UI still shows the old version
Check what the server actually runs first:
jt-proxense version
sudo systemctl status jt-proxense --no-pager | head -5
If that is the new version, your browser is holding the previous
index.html. The page detects a stale bundle and reloads itself, but a
hard refresh settles it immediately: Ctrl+Shift+R
(⌘+Shift+R on macOS).
How do I roll back to the previous version?
Install the earlier tag. Your database and settings are outside the install directory and are not touched:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh \
| sudo JT_PROXENSE_REF=v1.1.0 bash
Database migrations are forward-only. Rolling back across a release that added
one is not supported — take a copy of
/var/lib/jt-proxense/jt-proxense.db before a major upgrade if you want
a way back.
The service will not start
REFUSING TO START: config.yaml could not be parsed
The file is not valid YAML. The daemon refuses rather than falling back to defaults, because those defaults turn authentication off and bind every interface — a broken file must never quietly open an instance up.
Restore the copy taken before the last change:
ls -lt /opt/jt-proxense/config_backups/
sudo cp /opt/jt-proxense/config_backups/config_YYYYMMDD_HHMMSS.yaml \
/opt/jt-proxense/config.yaml
sudo chown jt-proxense:jt-proxense /opt/jt-proxense/config.yaml
sudo chmod 600 /opt/jt-proxense/config.yaml
sudo systemctl restart jt-proxense
A setting this version does not recognise is not an error — since v1.1.1 it is ignored with a warning naming the key, and everything around it is kept.
systemd: Failed to start / Unit ... failed with a ReadWritePaths error
Since v1.1.0 the unit names each writable path individually, and systemd
refuses to start a unit whose ReadWritePaths mentions something that
does not exist. Re-running the installer creates them:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
The paths are /opt/jt-proxense/config.yaml,
/opt/jt-proxense/config_backups,
/opt/jt-proxense/.ssh, /var/lib/jt-proxense and
/etc/jt-proxense. This happens if the unit was updated by hand rather
than through the installer.
Address already in use / port 8098 is taken
Find what holds it, then either stop that or move jt-proxense:
sudo ss -tlnp | grep 8098
To move it, edit server.http_port in
/opt/jt-proxense/config.yaml and restart. On a fresh install you can
set it up front:
curl -fsSL … | sudo JT_PROXENSE_PORT=8099 bashThe page loads but has no styling, or the layout is flat
The browser got the JavaScript but not the stylesheet, usually because the files are not readable by the service account after a manual copy:
sudo chmod 644 /opt/jt-proxense/dist/index.html \
/opt/jt-proxense/dist/assets/index-*.js \
/opt/jt-proxense/dist/assets/index-*.css
sudo systemctl restart jt-proxense
Re-running the installer fixes this too, and is the safer choice.
Adding a cluster from the UI fails to save
The service writes config.yaml and takes a backup first, so both
must be writable by the service account. Editing the file with a root shell
redirect changes its owner:
sudo chown jt-proxense:jt-proxense /opt/jt-proxense/config.yaml
sudo chmod 600 /opt/jt-proxense/config.yaml
sudo chown -R jt-proxense:jt-proxense /opt/jt-proxense/config_backups
sudo systemctl restart jt-proxenseCannot sign in
Too many attempts, please try again later
That IP hit the login rate limit: five failed attempts in five minutes. It clears itself after the cool-down, or immediately:
sudo jt-proxense unlock # list what is locked
sudo jt-proxense unlock --all # clear every lockout
This only clears the counter. It never changes a password.
Forgot the admin password, or lost the authenticator
The CLI works without the service running — that is what it is for:
sudo jt-proxense reset-password admin
sudo jt-proxense user reset-totp adminCompletely locked out after an auth misconfiguration
Turn authentication off, fix the configuration, turn it back on:
sudo jt-proxense auth disable # also binds to 127.0.0.1 only
sudo systemctl restart jt-proxense
Re-enable it with sudo jt-proxense auth set-local.
After upgrading to v1.1.0 or later
Everyone gets locked out after a few failed logins
The most likely upgrade surprise. X-Forwarded-For is now trusted
only from loopback or an address you name. If your reverse proxy runs on
another host, every request now carries the proxy's address, so all users
share one rate-limit bucket — five failed logins from anyone locks out everyone for
fifteen minutes. The audit log records the proxy as the source too.
Name the proxy in /opt/jt-proxense/config.yaml:
auth:
trusted_proxies:
- 192.0.2.10 # or a CIDR: 192.0.2.0/24
Then sudo systemctl restart jt-proxense and
sudo jt-proxense unlock --all. The log says
ignoring X-Forwarded-For from … once per peer while this is happening.
The UI stops working when served from another origin
Cross-origin access is closed by default from v1.1.0. If you serve the SPA from a different host or port than the API, name that origin:
server:
cors_origins:
- https://ui.example.net
A * entry is ignored on purpose: a wildcard combined with
credentials lets any site your browser visits drive this API with your session.
If the UI and API are the same origin — the default, because the app serves its own SPA — you need none of this.
The log says: ignoring unrecognised setting(s)
Informational. Your config.yaml contains a key this version does
not know — usually a typo, or an option from a different version. The key is
dropped and everything else is honoured; check the spelling against
config.example.yaml.
Before v1.1.1 this stopped the service from starting. If you are on v1.1.0 and the daemon refuses to start over a keyword argument, upgrade.
A user with a per-cluster role was refused before, and works now
Expected. Until v1.1.0 the role check read only the global (*)
grant, so jt-proxense user grant bob cluster1 operator was ignored at
the door. It is now honoured for endpoints under that cluster. Grants scoped to one
cluster still do not apply to user administration or global settings.
Review who has what:
sudo jt-proxense user listUninstall
How do I remove it completely?
One line. It stops and disables the service, then deletes the install
directory, /var/lib/jt-proxense (users, audit log, cluster
secrets), /etc/jt-proxense (the master key) and the service
user:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/uninstall.sh | sudo bash
It asks you to type remove first. Add --yes to skip
that in a pipeline: … | sudo bash -s -- --yes.
This is irreversible. Export first if there is any chance you want the instance back — see the next entry.
I want to remove it but keep my settings and users
Export before uninstalling. The bundle holds config.yaml, the
SQLite database (users, roles, audit log, notes) and the master key that
decrypts the stored cluster secrets, in one passphrase-encrypted file:
sudo jt-proxense export-config /root/jt-proxense-backup.enc
Copy it somewhere off the host, then uninstall. To bring it back on any machine:
curl -fsSL https://raw.githubusercontent.com/jasoncheng7115/jt-proxense/main/install.sh | sudo bash
sudo systemctl stop jt-proxense
sudo jt-proxense import-config /root/jt-proxense-backup.enc --force
sudo systemctl restart jt-proxense
The bundle contains the master key, so treat it as a credential and delete it once imported.
The uninstaller says: run as root, or no terminal for confirmation
It needs sudo for the same reasons the installer does. The
second message means it was piped with no TTY to read your confirmation from,
which is what --yes is for:
curl -fsSL … /uninstall.sh | sudo bash -s -- --yesSomething was left behind after uninstalling
The uninstaller tolerates missing pieces, so a partial state clears with the same commands run by hand:
sudo systemctl stop jt-proxense; sudo systemctl disable jt-proxense
sudo rm -f /etc/systemd/system/jt-proxense.service /usr/local/bin/jt-proxense
sudo systemctl daemon-reload
sudo rm -rf /opt/jt-proxense /var/lib/jt-proxense /etc/jt-proxense
sudo userdel jt-proxense
If you installed somewhere else, substitute that path — the uninstaller honours
JT_PROXENSE_INSTALL_DIR too.
Still stuck
Nothing here matches. How do I report it?
Open an issue with enough to reproduce it:
jt-proxense version
cat /etc/os-release | head -2
python3 -V
sudo journalctl -u jt-proxense -n 100 --no-pager
Redact PVE hostnames and API tokens before pasting. Never paste
config.yaml as-is — it contains credentials.
No entry matches that. Try fewer words, or open an issue.