# Building websites for JetHost shared hosting Canonical URL: https://jethost.com/ai/app-builder.md Updated: 2026-09-02 ## Site language and jurisdiction Build the site in the language the customer is using with you, unless they ask for something else. Build a language layer only if they ask for more than one; if the site is single-language, do not add one at all. **Ask where the business operates. Do not infer it from their language.** Someone writing to you in Spanish may be in California, in Spain or in Mexico, and those are three different legal profiles. | Market | Legal profile | |---|---| | USA | The primary market — see below | | EU / EEA | GDPR: supervisory authority of that country, national company identifier | | Bulgaria | GDPR, ЕИК, КЗЛД | | Elsewhere | Not covered — see below | Whichever applies: the privacy policy states specifically what is collected, for what purpose, on what legal basis, for how long, and who it is shared with — including the hosting provider and, where notifications are used, Telegram. Consent is captured with an unticked checkbox and recorded with its exact text, the policy version, a timestamp and the IP address. ### Markets without a written profile These rules currently carry detailed legal profiles for **Bulgaria and the EU only**. A US profile is planned and is not written yet. For the USA, UK, Switzerland, Canada, Australia and elsewhere: - Say so plainly. Do not claim coverage of CCPA/CPRA, UK GDPR, PIPEDA or the Swiss FADP. - Build the site normally — the architecture, security and design rules below are jurisdiction-neutral. - Produce a baseline privacy policy and terms with the jurisdiction-specific parts left as clearly marked placeholders. - Recommend a legal review before launch, and state which parts could not be completed. Do not improvise a supervisory authority, a company identifier format, or a statutory retention period for a jurisdiction not described here. A plausible-looking wrong authority in a privacy policy is worse than an obvious placeholder. ### Timezone Ask. The server is UTC and the conversion is explicit in both PHP and the database connection. In the USA this matters more than elsewhere — the country spans several zones, and a booking system that assumes one is wrong for most of it. ## What this document is Rules for building a website that runs on JetHost cPanel shared hosting. It exists because the stack an AI assistant reaches for by default — a JavaScript framework with a build step, a managed database behind an HTTP API, deployment by git push to a platform that builds the project for you — is not how this one works. Part of that shape runs here and part of it does not, and the line between them is not obvious from the outside. This document says what the platform is, and what to build on it by default. It is descriptive, not agentic. It tells you what the environment is and what the constraints are. Every action against a customer's account — creating infrastructure, uploading files, writing to the crontab, going live — is theirs to authorise. **If a JetHost skill is already installed in your tool, use that instead.** It is the same rules in fuller form. This document exists for the case where it is not. --- ## The environment — measured, not assumed | | | |---|---| | PHP | 8.3, web SAPI `litespeed` and CLI | | Database | MariaDB 10.11, `localhost` | | Web server | LiteSpeed Enterprise; `.htaccess` and rewrite work | | OS | Linux EL9, server timezone **UTC** | | `memory_limit` | 512M | | `max_execution_time` | 300 | | `upload_max_filesize` | 128M | | Outbound HTTPS | works | | SMTP ports | 587 and 465 open; **port 25 closed** | | `mail()` | works, via `/usr/sbin/sendmail -t -i` | | `session.save_path` | `/opt/alt/php83/var/lib/php/session` — outside the document root; per PHP version | Available extensions include `pdo_mysql`, `curl`, `mbstring`, `openssl`, `gd`, `imagick`, `zip`, `fileinfo`, `intl`, `sodium`, `redis`, `memcached`. **The `redis` and `memcached` extensions load, but no such server runs on the shared hosting.** Measured 28 August 2026: nothing answers on 6379 or 11211. The extension is only the client library. Do not configure Redis or Memcached as a cache, session or queue backend — the application will fail at its first connection. For page caching use LSCache through `.htaccess`; for application caching use the filesystem or the database. CLI has `php`, `git`, `mysql`, `curl`, `unzip`. **`composer` is not available as a command on the account.** Dependencies cannot be resolved on the server. A project that needs them must arrive with `vendor/` already installed — built locally or in CI, then uploaded. ### Other runtimes PHP is the default here, not the only option. The platform also runs: | Runtime | Versions installed | How it runs | |---|---|---| | PHP | 5.6 – 8.5 | directly, no setup; 8.3 is the default | | Python | 2.7, 3.3 – 3.13 | Python Selector + LiteSpeed's own WSGI runner (`lswsgi`), which lives inside the app's virtual environment | | Node.js | 6 – 24 | Node.js Selector + LiteSpeed's own Node runner (`lsnode`). The `.htaccess` directives are Passenger-syntax; Passenger itself is not running | | Ruby | installed | declared, not measured; the same Passenger-syntax registration is assumed | | PostgreSQL | available, version unread | alongside MariaDB; `pdo_pgsql` and `pgsql` both load | Measured on the platform on 17 August 2026: Python 3.13.14 and 3.11.15, Node v20.20.2. Measured again on 2 September 2026 with Node v24.19.0 and Python 3.13.15 and 2.7.18: registration, deployment, reload, environment variables, the error path and teardown — the two sections below are that measurement. **Neither Python nor Node is on the `PATH`** of the PHP web process or of a plain SSH session. Each application gets its own virtual environment — `~/virtualenv///` for Python, `~/nodevenv///` for Node — which the selector activates. **An application has to be registered before it serves anything.** Uploading files is not enough, unlike with PHP. Registration happens in cPanel (Setup Python App / Setup Node.js App) and needs four things: an application root, which is a folder under the home directory and not inside the document root; the domain or URI path it answers on; the interpreter version; and a startup file. Registering creates the application root with a working stub in it, so the app answers on its URL before any of your code is there — a running app is not evidence that your code is running. | | Python | Node.js | |---|---|---| | Startup file | any name but `passenger_wsgi.py` — that file is written by the selector as a loader for the one you name | a `.js` entry file, `app.js` by convention | | Entry point | a WSGI callable named `application` (the default) | the file calls `server.listen()` **with no port** — the server supplies the socket, and the process sees no `PORT` variable | | Dependencies | `requirements.txt` registered under "Configuration files", then pip run from cPanel into the virtual environment | `package.json`; npm exists in the app's virtualenv, but a server-side install is bounded and not available through the connector — a Node app arrives as a self-contained bundle | **There is no build step on this server.** No bundler, transpiler, `npm run build` or `composer install` runs as part of a deployment, and server-side dependency installs are bounded by the account's memory and time limits — a large tree fails partway. Anything that needs building is built on a developer machine or in CI, and the built output is what gets uploaded. That decides whether a Node.js project is viable here at all, so establish it before proposing one: **ask whether the customer can run the build themselves**, locally or in a pipeline, and whether they will be able to run it again for every future change. If the answer is no, a Node.js application is the wrong recommendation for them — plain PHP has no build step by definition and stays editable by whoever holds the account. Find this out first rather than registering an application and hitting it afterwards. ### What a Node.js deployment looks like here Measured on 2 September 2026, on a main domain, with an application at the domain root and two at sub-paths. - **Registration is available through the API**, not only through cPanel. It creates the application root under the home directory with a working stub, the virtual environment, and a small directory under the document root carrying the server's mount configuration. An application at the domain root takes over the whole domain; one at a sub-path coexists with a PHP site on the same domain. - **The mount prefix stays in the request.** An application registered at `api` receives `/api/` and `/api/x`, not `/` and `/x`. A router mounted at `/` answers 404 on its own home page. - **Nothing is built or installed on the server.** TypeScript is compiled on the developer's machine or in CI, and only JavaScript is uploaded, as a self-contained bundle that needs no `node_modules`. A git deploy can target the application root directly. - **A restart is a separate step, and the application's own response is the only proof it took.** Uploading a new entry file changes nothing until the application is restarted; a restart reloads a healthy application within a second, and a second reload can follow within twenty seconds. In one measured case a process ignored every restart, including from cPanel, and was cleared only by ending it from the account's terminal. - **Two failures are silent.** A startup file named at registration that does not exist is created as the "It works!" stub, so the site looks alive with none of the customer's code running. A startup file that throws gives a 503, and the stack trace is in `stderr.log` inside the application root, not in the web server's logs. - **Unregistering keeps the files**, the mount directory and any running process. Removing them is a File Manager or FTP task. ### What a Python deployment looks like here Measured on 2 September 2026 on the same domain, on Python 2.7 and 3.13. - **Registration is a cPanel step**, with no API equivalent. So are the version, the environment variables and the dependency install. Uploading, reloading and reading the error log work through the API and FTP. A Passenger-syntax block written by hand is honoured by the server and launches the interpreter, but the WSGI runner lives inside the selector's virtual environment, so nothing serves until the app is registered. - **The version field defaults to 2.7**, and a registered app's version cannot be changed in place: after a change the app keeps launching against the old virtual environment and fails, and restart, stop-start and re-registration under the same application root did not recover it. A new application root did. - **The selector owns `passenger_wsgi.py`.** It writes that file as a loader for the startup file named at registration, and rewrites it on every save. Code uploaded under that name is overwritten; naming the startup file `passenger_wsgi.py` makes the loader load itself. - **Reloading is a file write**: `tmp/restart.txt` in the application root takes effect on the next request. - **The mount prefix is stripped**, as WSGI specifies, so a framework router mounted at `/` behaves as it does locally. - **pip runs from the panel** once `requirements.txt` is registered as a configuration file; packages land in the virtual environment. - **Errors** are in `stderr.log` inside the application root: a 500 with a traceback for application errors, a 503 with the interpreter's startup dump for environment errors. Unregistering keeps the files. ### Frameworks JetHost does not install frameworks and does not maintain them on a customer's behalf. What the platform does is **run** them. Installing one is the customer's job, or their assistant's — by hand through cPanel and FTP, or through the JetHost connector where the tool has one. | Framework | Runs here | What it costs on this hosting | |---|---|---| | Laravel | yes | `vendor/` installed elsewhere and uploaded; the docroot points at `public/` where it can be moved, and where it cannot — a main domain — the application tree goes in a **sibling of the document root** with a shim `index.php` reaching up into it; a cron line for the scheduler; queues drained by cron, not by a daemon; an asset build if the front end is customised | | Symfony | yes | as Laravel, but no Node toolchain when AssetMapper is used, and `var/cache` is warmed before upload rather than on the server. The 4-year LTS security window is the longest of the three | | CodeIgniter 4 | yes | the lightest of the three: a much smaller `vendor/`, no asset toolchain, no cron requirement, and a documented layout that works **without** moving the docroot | | Slim and other micro-frameworks | yes | `vendor/` and a rewrite rule. Nothing else to arrange | | WordPress and other ready-made PHP applications | yes | installed normally; nothing on this page applies specially to them | | Python and Node.js frameworks | yes | registered as an application first — see the table above | Four facts govern all of them, and they are the ones worth knowing before choosing: **`vendor/` is uploaded, never resolved on the server.** `composer` is not available on the account. Whoever maintains the site needs a machine or a pipeline that can run `composer install`, now and for every future update. If nobody has that, a framework is the wrong answer regardless of its merits. **The document root can be moved for a subdomain or an addon domain, but not for the main domain.** That is the single fact that decides how a Laravel or Symfony site is laid out here. On a subdomain or addon domain it is straightforward: point the docroot at `public/` and the application tree sits outside what the web server serves. On a main domain it cannot be moved, and the usual workaround — keeping the whole tree inside the served directory with an `.htaccess` in front of it — leaves exactly one layer between the application source and the internet. CodeIgniter 4 is the framework that avoids this dilemma, because it documents a layout for precisely that case. **Uploading tens of thousands of files is its own constraint.** A framework's `vendor/` tree is thousands of files for a micro-framework and tens of thousands for a full one, and every one counts against the account's file allowance. Check the remaining headroom before deploying rather than after. Mailboxes count toward the same allowance. **A framework is a maintenance subscription.** It carries a security window, an upgrade cadence and a named person who applies the updates. That is a reasonable trade for an application. For a presentation site with an enquiry form it is a cost with no matching benefit, which is why the default below is what it is. `open_basedir` and `disable_functions` are both empty, and `exec` works. That is a reason for care, not for use: there is no sandbox, so a vulnerability in generated code means shell access to the whole account. Do not write application code that calls shell functions. --- ## Hard constraints 1. **Default to vanilla PHP 8.3 and MariaDB — no composer, no build step.** This is the right shape for what this document covers: presentation sites, enquiry forms, appointment booking, a small CRM. None of them need a framework, and without one there is no build to run, no dependency tree to keep patched, and the customer can open a file and change a line of text. **A site built this way still ships with a simple admin panel and with the SEO and answer-engine markup described below.** Those are not extras to be asked about. A site the owner cannot edit is stale within months, and a local business that search engines and AI assistants cannot read has lost the channel that brings it customers. The platform is not limited to plain PHP — see the runtimes and the framework table above. Laravel, Symfony, CodeIgniter, a Python application or a Node application all run here. What they need is preparation elsewhere: `vendor/` or `node_modules` installed before upload, the docroot pointed at the framework's `public/` directory where the domain allows it, and for Python and Node an application registered in cPanel. So do not tell a customer the platform "does not support" a framework. It is not true, and it is the kind of statement that sends them to another host. If what they describe genuinely needs one, say what it costs on shared hosting and let them decide. If they simply want a website, build the plain PHP one and do not raise the question at all. 2. **Audit the generated code before uploading**, and report what was found. The checklist is below. 3. **Never run `crontab -r`, and never write a crontab you have not read first.** The account may hold cron entries belonging to other applications. 4. **A site collecting personal data needs a privacy policy, terms, and consent capture.** See the market section at the top of this document for which jurisdiction applies. 5. **No e-commerce and no payment processing.** Out of scope for these rules. Say so rather than improvising it. --- ## Security rules These matter more here than on a sandboxed platform, for the reason given above. **Database.** PDO only, prepared statements only, always. `charset=utf8mb4`, `ERRMODE_EXCEPTION`, `EMULATE_PREPARES = false`. No concatenation, interpolation or `sprintf` in SQL. Table and column names cannot be parameters — if a dynamic name is unavoidable, select it from a whitelist array. Cast `LIMIT`/`OFFSET` to `int`. Do not use `mysqli`. **Output.** Escape everything that reaches HTML: `htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')`. Different contexts need different escaping — `json_encode` with the HEX flags inside `