JS Stats runs on ordinary cPanel shared hosting. Upload the files, enter your database details, add one script tag. No VPS, no Docker, no Node.js, no build step — about 15 minutes.
That is the whole list. No shell access, no Composer, no Node.js, no Docker, no cron access strictly required (the crons add retention, rollups, alerts and uptime, but the analytics work without them).
PHP 7.4 or newer (8.x recommended) with the pdo_mysql and mbstring extensions, and a MySQL or MariaDB database. Every mainstream cPanel plan has all of this by default. You do not need shell access, Composer, Node.js, Docker or a VPS.
In cPanel open MySQL Databases, create a database and a user, then add the user to the database with ALL PRIVILEGES. Note the database name, username and password.
Extract the archive and upload it to your account. Point the domain or subdomain document root at the public/ folder. If your host will not let you move the document root, upload everything into public_html instead — the bundled .htaccess handles that layout too.
Copy config/config.example.php to config/config.php and fill in the db block, then set app_url to the address you will use. Keep config.php out of version control: it holds your credentials and your ip_salt.
Visit your domain in a browser. On first run the schema is imported automatically and you are asked to create the first admin account. The installer disables itself immediately afterwards and will simply redirect to the login page from then on.
Add a site in the dashboard, open its Tracking code, and paste the one-line script tag into your site before </body>. Data appears in real time — open Real-time and load a page to confirm. WordPress users can install the bundled plugin from wp-plugin/ instead.
In cPanel add the crons you need. Daily is fine for prune (data retention), rollup (keeps large date ranges fast) and lifecycle; every 5 minutes suits alerts and uptime. Each is a plain CLI script, for example: php /home/USER/js-stats/cron/rollup.php
All optional, all plain CLI scripts. Add them in cPanel → Cron Jobs, replacing the path with your own.
Country data without Cloudflare. Import a free IPv4→country CSV once with php cron/import_geoip.php ranges.csv. Countries are resolved and stored per hit as it arrives, so importing only affects new traffic — do it early. Behind Cloudflare the CF-IPCountry header is used automatically and no import is needed.
Push-based real-time. The Real-time view polls every 15 seconds out of the box. If your host allows a Node process, the self-contained server in realtime-node/ upgrades that to websockets; if it is absent the app falls back to polling on its own.
WordPress. Install the plugin in wp-plugin/ and paste your tracking ID — it injects the snippet for you.
Upload the new files over the old ones, keeping your config/config.php. Schema changes apply themselves on the next dashboard load, so there is no migration command. Hard-refresh once afterwards if the styling looks stale.
Almost always the database block in config/config.php. Check the name, user and password, and that the user was added to the database with full privileges. Your host's error log will name the exact cause.
PHP is not executing for that directory — usually the document root points somewhere unexpected, or the file was uploaded with the wrong extension. Confirm the domain's document root is the public/ folder.
Clean URLs need Apache's mod_rewrite and the bundled .htaccess. Make sure .htaccess actually uploaded — many FTP clients hide dotfiles by default.
Check the snippet is on the page and the tracking ID matches the site. Headless browsers and bots are filtered out by design, so test with a normal browser. If you set enforce_site_domain, confirm the site's domain field matches where the snippet runs.
No. That is the main reason this exists. It is plain PHP and MySQL, deployed by uploading files, so it runs on the same $2–5/month shared hosting most sites already use. Most privacy-analytics tools require Docker or a VPS.
In the default cookieless mode the tracker stores nothing in the browser — a visitor is identified by a daily-rotating server-side hash — and raw IP addresses are never stored, only salted SHA-256 hashes. Consent rules vary by jurisdiction, so confirm with whoever advises you on compliance, but there is no tracking cookie to disclose.
The tracker is about 2 KB, loads asynchronously and reports with sendBeacon, so it never blocks rendering. The dashboard is server-rendered; only charts and live polling use JS.
Daily rollup tables back the date-range charts, so dashboards stay fast as the hits table grows. Run cron/rollup.php daily once you are past a few hundred thousand pageviews.
Yes. Add as many sites as you like, each with its own tracking ID and timezone. If one snippet is installed across a main domain and its subdomains, every report can be filtered to a single subdomain.
Upload the new files over the old ones and reload the dashboard. Schema changes apply themselves — there is no migration command to run. Leave config/config.php alone.
JS Stats is also available as a hosted service at stats.jidanshoppu.com — no install at all. For the self-hosted package, get in touch at stats@jidanshoppu.com.