Step-by-step guide — Part 1: Install
Part 1 of the step-by-step guide: installing TWS Panel on a clean VPS, from scratch all the way to the setup wizard. The README has the short version; this guide explains every step, what shows up on screen, and what to do when something goes wrong.
The steps are numbered 1 to 8. The panel’s scripts (install.sh, uninstall.sh) refer to
these numbers.
| Step | Where | What it does |
|---|---|---|
| 1 | Provider | Get a VPS |
| 2 | Your computer → VPS | Sign in as root and check the system |
| 3 | VPS | Create your non-root user |
| 4 | Your computer | Generate the SSH key and install it on the VPS |
| 5 | VPS | Install git |
| 6 | VPS | Clone the repository into /opt/tws-panel |
| 7 | VPS | Run the installer |
| 8 | Browser | Open the panel and follow the wizard |
After Step 8, the wizard’s Security step has its own guide: Security wizard. Then Part 2 continues the setup.
Before you start
Section titled “Before you start”What to get
Section titled “What to get”A VPS (virtual server) with:
- Ubuntu 22.04 or 24.04 LTS, clean (no other software installed). When creating it, prefer 24.04.
- Recommended minimum: 1 vCPU, 2 GB of RAM, and 25 GB of disk.
- Ports 80 and 443 open to the internet. The HTTPS certificate depends on them. If your provider has a firewall in its hosting dashboard, open both.
- For email: port 25 unblocked by the provider. Many providers block it, especially on new accounts. You check this in Step 4 of Part 2.
You will need two things the provider gives you: the VPS IP and the root password. Before going on, look in the provider’s dashboard for “Console”, “VNC”, “Rescue”, or “Recovery mode” and confirm it exists: it is your way back in if you lose SSH access. See I lost access.
Domain (optional, but recommended)
Section titled “Domain (optional, but recommended)”Without a domain, everything works with automatic *.sslip.io addresses (for example,
https://203-0-113-10.sslip.io). With a domain of your own, you get:
- your own address for the panel (
panel.example.com), instead of one with the IP in the name; - professional email, which requires a domain;
- your own addresses for your projects.
You will create records in the domain’s DNS (at the provider where it is registered, or on Cloudflare). The examples in Part 2 use Cloudflare. You can leave the domain for later: nothing in Part 1 depends on it.
Accounts
Section titled “Accounts”| Account | What for | Required? |
|---|---|---|
| GitHub | Publish projects from a repository. A private repository needs a read-only token (the screen teaches you how to generate one). | Only if your code is on GitHub |
| Cloudflare | R2, the external storage for backups (the free plan covers 10 GB per month; it asks for a card). It also works as the domain’s DNS. | Recommended |
| Telegram | Receive the panel’s notices on your phone. | Optional |
None of them is used in Part 1; you only need them in Part 2.
Reinstalled the VPS?
Section titled “Reinstalled the VPS?”⚠️ Got an error when connecting: "REMOTE HOST IDENTIFICATION HAS CHANGED!"
If, when trying to connect, you saw this in big capital letters and the connection was refused:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!...Host key verification failed.What happened. SSH keeps the “fingerprint” of every server you have signed in to. The fingerprint the server just presented is different from the one stored on your computer — and SSH would rather refuse the connection than connect you to a server that may not be yours.
When this is expected: you reinstalled the VPS operating system, switched VPS, or the provider reassigned that same IP to another machine. In those cases the server really is a different one, and the old fingerprint is no longer valid.
The fix — delete the old fingerprint for that IP and connect again. Run on your computer:
ssh-keygen -R YOUR_IPssh root@YOUR_IP(YOUR_IP is a placeholder: replace it with your VPS IP.)
It will ask whether you trust the new identity — answer yes. That’s it, the connection goes back
to normal.
🔄 Already had the panel installed and want to start over?
Before reinstalling the whole machine, check whether that is really your case — almost always it isn’t.
I just want to redo the setup wizard. You don’t need to reinstall anything. On the VPS:
cd /opt/tws-panelsudo ./scripts/reset-setup.sh # the wizard goes back to step 0sudo ./scripts/show-token.sh # shows the setup token againYour projects, domains, email and security history stay intact. Use --full on the first
command if you also want to delete the panel’s administrator account.
I want to remove the panel and install it again, without reinstalling the system. Useful for testing the installation as many times as you like. On the VPS:
cd /opt/tws-panel./scripts/uninstall.sh --dry-run # only shows what would be deleted; changes nothing./scripts/uninstall.sh # shows it again and asks you to type "remover"(The script still prints in Portuguese: to confirm, type the word remover, which means “remove”.)
It deletes everything the panel created: the panel and its containers, the proxy (Caddy), the
email server, the deployed projects with their data, the /opt/tws-projects folder, the hardening
scripts copied to the VPS and, last of all, the /opt/tws-panel folder itself. Before deleting, it
lists exactly what it found. Afterwards, move to another folder (cd ~) and start over from Step 6
(cloning the repository) — the git from Step 5 is still installed. Want to keep the repository folder?
Use --keep-repo and go straight to Step 7.
I want a truly clean machine. In that case you do need to reinstall the operating system, and it happens in two places:
1. In your VPS provider’s panel. Look for “Reinstall”, “Reimage” or similar — the name varies by provider. Choose Ubuntu 24.04 LTS and write down the new root password it generates; the old one stops working.
2. On your computer. Since the system is a different one, the server now identifies itself with a
different key, and your next connection will be refused with the warning
REMOTE HOST IDENTIFICATION HAS CHANGED!. This is expected, and the fix is in the block right above:
⬆️ Got an error when connecting: “REMOTE HOST IDENTIFICATION HAS CHANGED!”
After that, just follow from Step 1 onwards, as with a new VPS — because now it is one.
Step 1 — Get a VPS
Section titled “Step 1 — Get a VPS”Get a VPS running Ubuntu 24.04 LTS (recommended minimum: 1 vCPU / 2 GB RAM / 25 GB disk), as described in Before you start.
Step 2 — Sign in as root via SSH
Section titled “Step 2 — Sign in as root via SSH”Sign in to the VPS and confirm the OS version:
ssh root@YOUR_IPcat /etc/os-release # expected: PRETTY_NAME="Ubuntu 24.04.x LTS" (or 22.04)🔐 First time connecting via SSH? What shows up and what to answer
On the first connection to any new server, SSH shows this warning:
The authenticity of host '203.0.113.10 (203.0.113.10)' can't be established.ED25519 key fingerprint is SHA256:Xk9vN2mQpL7dR4wT8yB3cF6hJ1uA5sE0gH9zK2xW4vM.This key is not known by any other names.Are you sure you want to continue connecting (yes/no/[fingerprint])?What to answer: type yes and press Enter.
What is this? The fingerprint is the server’s “digital signature” — it is how your computer recognizes the VPS on later connections. The warning appears only the first time; after that the fingerprint is saved and the connection is direct.
Common errors:
- “I type the password and nothing shows up” — that’s normal! The terminal shows no characters
(not even
*) while you type passwords. Type calmly and press Enter. Permission denied (publickey,password)— wrong password, or the VPS provider requires an SSH key. Check the password in the provider’s panel and try again.- The fingerprint warning shows up again after reinstalling the VPS — normal, the machine
changed. Remove the old entry with
ssh-keygen -R YOUR_IPand connect again.
Step 3 — Create your non-root user
Section titled “Step 3 — Create your non-root user”This is the user who will operate the VPS from now on:
adduser YOUR_USER # replace YOUR_USER with any name you like; you choose the password on the spotusermod -aG sudo YOUR_USER # grants administrator permission (sudo)
# --- Mistyped the password, gave up midway or want to start over? Choose ONE of the two: ---passwd YOUR_USER # sets/resets only the password, keeping the userdeluser --remove-home YOUR_USER # deletes the user and their folder, so you can create everything again(YOUR_USER is a placeholder for the username you choose.)
👤 Stuck on adduser? What shows up and what to fill in
When you run adduser YOUR_USER, the system asks a series of questions. In the examples below
we use “kelvin” as an example name — in your case the username you
chose will appear. This is how it looks:
Adding user `kelvin' ...Adding new group `kelvin' (1000) ...Adding new user `kelvin' (1000) with group `kelvin' ...Creating home directory `/home/kelvin' ...Copying files from `/etc/skel' ...New password:Retype new password:passwd: password updated successfullyChanging the user information for kelvinEnter the new value, or press ENTER for the default Full Name []: Room Number []: Work Phone []: Home Phone []: Other []:Is the information correct? [Y/n]What to fill in, step by step:
New password:— create a strong password that you will remember (it is your user’s password, used for SSH and forsudo). Note: nothing shows while you type — not even*. That’s normal, the password is being recorded. Type it and press Enter.Retype new password:— repeat the same password.Full Name,Room Number,Work Phone,Home Phone,Other— optional data. You can leave everything blank: just press Enter on each one.Is the information correct? [Y/n]— typeY(or just Enter) to confirm.
Common errors:
-
Sorry, passwords do not matchfollowed byTry again? [y/N]— the two passwords you typed were different. Answeryto type them again. Beware of the capitalN: it is the default, so just pressing Enter is the same as answeringn.What if I already answered
n? Thenaddusergives up on the password but keeps going and creates the user — you will seepasswd: password unchangedand, at the end,Adding new user .... The user exists, but has no password, and cannot sign in over SSH. Nothing is lost: set the password now, without recreating anything.Terminal window passwd YOUR_USERIf you prefer to start from zero, delete the user and run
adduseragain:Terminal window deluser --remove-home YOUR_USER -
I typed
n(or anything) atRoom Number/Work Phone— these are purely cosmetic fields, they affect neither login nor permissions. If you want to clear them:chfn YOUR_USERand press Enter on each field. -
BAD PASSWORD: ...— weak password warning. The system accepts it, but prefer a long one (a phrase with words + numbers, e.g.:horse-battery-42-window). -
“I think I mistyped because I saw nothing” — no problem: if you got it wrong,
addusercomplains (Sorry, try again.) and asks again.
Check that it worked before moving on:
id YOUR_USERsudo must appear in the list of groups. If it doesn’t, the second command didn’t take.
Now end the root session. We are not reconnecting yet — the next step happens on your
computer, and exit already leaves you there:
exit # leaves root and closes the SSH connectionYou are back at your computer’s terminal. Step 4 continues from there.
🤔 What about su - YOUR_USER, won't that do?
It works, and it’s faster — but it has a side effect worth knowing about.
With su - YOUR_USER you stay inside the root session — just with another identity on
top. An exit returns you to root instead of ending access, and it is easy to forget that that
terminal still has a root session open underneath.
By signing in over SSH directly as your user, the session is theirs from start to finish: exit really
ends it, and anything that requires privilege goes through sudo — which asks for a password and is logged
in the system log. It is the difference between “I’m root wearing another hat” and “I’m a
regular user and I ask permission when I need to”.
This matters even more if you will leave the session open for a while: better that it be your user’s, not root’s.
And why don’t we use that shortcut here? Besides the root session that stays open, it
keeps you inside the VPS — and Step 4 happens on your computer. You would have to leave anyway,
so exit solves both things at once.
⏱️ Want to control how long your SSH session survives idle? (optional — read if the connection drops by itself when you stop typing)
If you have noticed the session closing after a few minutes idle, the culprit is almost always the VPS provider, not the server: network firewalls tend to drop connections that sit without traffic for a while. Your computer can fix this by sending a sign of life every minute. Run on your computer, not on the VPS:
# on YOUR computer (Linux/macOS). On Windows with PuTTY, the equivalent field is# "Seconds between keepalives" under Connection.printf 'Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3\n' >> ~/.ssh/configAfter that, reconnect. This does not weaken security — the session is still ended if the network really goes down; what changes is that it stops looking abandoned while you are working.
Step 4 — Generate your SSH key
Section titled “Step 4 — Generate your SSH key”Do this before going to the wizard.
The wizard’s Security step will turn off password login over SSH (hardening phase 02). From then on, the key is your way in — and the wizard asks for the public key right in the first phase, with an automatic 5-minute rollback running. By generating it now, with the terminal already open, you don’t have to stop midway to switch windows.
You just left the VPS, so you are already in the right place: the commands below run on your computer. That is where the key needs to exist so you can authenticate later — if it were generated on the VPS, it would be no use for signing in to it.
First, check whether you already have a key. If you already use SSH for something — GitHub, another server, work — it probably already exists, and generating another on top of it deletes the old one. Run:
ls -l ~/.ssh/*.pubOn Windows (PowerShell):
Get-ChildItem ~\.ssh\*.pubLook only at the file names that showed up and follow the first row of the table that matches what you saw:
| If the list shows… | Use this key | What to do now |
|---|---|---|
id_ed25519.pub (even if other files appear alongside it) | id_ed25519 | Don’t generate anything. Go straight to ⬇️ Install the key on the VPS (it’s a link — click it) |
id_rsa.pub, but not id_ed25519.pub | id_rsa | Don’t generate anything. Go to ⬇️ Install the key on the VPS and, there, use the commands with id_rsa |
Only files with other names (e.g. github_deploy.pub, some_service_deploy.pub) | none of them | Generate a new key in the next block |
No such file or directory (or nothing) | — | Generate a new key in the next block |
Why this rule, and not “pick whichever you like best”:
id_ed25519andid_rsaare names thatsshtries on its own every time you connect. Using one of them, signing in to the VPS works with no extra configuration on your computer.id_rsais older, but the server accepts it just the same.- Keys with other names were created for a specific service (a deploy, a company,
a tool). Don’t reuse them: besides
sshnot trying them on its own, mixing accesses means that whoever has that key also gets into your VPS. - Generating a new
id_ed25519, in those last two cases, deletes nothing — it doesn’t exist yet, so there is nothing to overwrite.
Now generate the key pair (only if the table above told you to generate). It is the same command on Linux, macOS, WSL and Windows PowerShell:
ssh-keygen -t ed25519It asks three questions, in this order:
| What shows up | What to do |
|---|---|
Enter file in which to save the key (...id_ed25519): | Just Enter — accepts the default location |
Enter passphrase (empty for no passphrase): | Type a passphrase. Nothing shows on screen, not even an asterisk |
Enter same passphrase again: | Repeat the same one |
The passphrase is an extra password that protects the key in case someone gets access to your computer. It is recommended, and so you don’t have to type it on every connection you can keep it in the session:
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519When the command finishes, check that the key was created:
cat ~/.ssh/id_ed25519.pubOn Windows (PowerShell) the command to display it is different:
Get-Content ~\.ssh\id_ed25519.pubA single line will appear, a long one, looking something like this:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH8k2p... your-user@your-computerIf the line showed up, the key is created.
Install the key on the VPS — now, while the password still works. The command always says which file to install; use the line for the key the table chose:
ssh-copy-id -i ~/.ssh/id_ed25519.pub YOUR_USER@YOUR_IPIf the table told you to use id_rsa, the line is this one (instead of the one above):
ssh-copy-id -i ~/.ssh/id_rsa.pub YOUR_USER@YOUR_IPIt asks for the adduser password one last time and writes your public key to the server. On
Windows (PowerShell), where ssh-copy-id doesn’t exist, the equivalent is:
Get-Content ~\.ssh\id_ed25519.pub | ssh YOUR_USER@YOUR_IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"With id_rsa, replace id_ed25519.pub with id_rsa.pub on that line. From here on, whenever
a command mentions id_ed25519 (the cat, the ssh-add), anyone using id_rsa makes the same swap.
Now test, and this is the test that matters:
ssh YOUR_USER@YOUR_IPIf you get in without it asking for the account password, it’s working. It may ask for the key passphrase — that is a different thing, it’s local, and has nothing to do with the server.
🔑 What if I lose, delete or corrupt the private key?
Prevention (do this before moving on):
-
Keep a copy of the private key somewhere safe, such as a password manager. Whoever has that copy can access the VPS — treat it like a password.
-
A more robust alternative: have a second key, generated on another device (another computer, or your phone). The wizard field accepts one key at a time, so install the second one after finishing the wizard, while the first one still works.
Take the public key from the other device (the contents of its
.pub), connect to the VPS from the computer that already has access and append the line:Terminal window echo "PASTE_THE_SECOND_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys(Replace
PASTE_THE_SECOND_PUBLIC_KEY_HEREwith the key itself.)Use
>>and not>— a lone>erases the existing keys and locks you out on the next connection. Check the result withcat ~/.ssh/authorized_keys: both lines should appear. That way, losing a device doesn’t leave you without access.
If you have already lost the key — or the password, or access to the panel — see I lost access: now what?, which covers each case and the way back.
The test above already left you connected to the VPS. From here on, all the steps are done there,
with that user — anything that needs administrator permission will ask for sudo and your password.
Step 5 — Install git
Section titled “Step 5 — Install git”sudo apt update && sudo apt install -y git📦 git was already installed. Is that normal?
Yes, it’s normal. “Ubuntu 24.04 LTS” in your provider’s panel is almost never the raw system from
Canonical: it is a template assembled by the provider, and git, curl, wget and vim usually come
in that package. Finding git already there on a freshly created machine doesn’t mean someone got into
it.
The Step 5 command is still valid: if the package is already there, apt replies
git is already the newest version and does nothing.
Want to confirm instead of trusting? These are three read-only checks, none of which alters the system:
dpkg -V git # silence = files identical to the official package'sapt-cache policy git # the origin should be archive.ubuntu.com or security.ubuntu.comlast -a | head # only your own logins should appeardpkg -V compares each installed file against the package’s official signature — no output
is the good result. In apt-cache policy, what matters is the origin: an unknown repository
there would be a reason to stop and investigate.
🔑 First sudo: the giant warning and the password that doesn't show
The first time you use sudo with a new user, a classic warning appears:
We trust you have received the usual lecture from the local SystemAdministrator. It usually boils down to these three things:
#1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility.
[sudo] password for kelvin:What to do:
- The warning is just welcome ceremony (a Linux tradition) — it doesn’t require an answer.
- At
[sudo] password for kelvin:(on your terminal the name of the user you created will appear, not “kelvin”), type YOUR user’s password (the one you created inadduser), not the root password. - Remember: nothing shows on screen while you type — not even
*. Type it and Enter.
That long warning only appears once. After that sudo asks for the password directly — and, for
a few minutes, not even that (it “remembers” that you authenticated).
Common errors:
-
Sorry, try again.— wrong password. You get 3 attempts before the command fails. -
kelvin is not in the sudoers file— the user doesn’t have administrator permission, because theusermodfrom Step 3 never ran for this user. Sincesudoitself is blocked, the fix has to be made as root. On your computer, open the root session, grant the permission and confirm:Terminal window ssh root@YOUR_IPusermod -aG sudo YOUR_USERid YOUR_USER # must list "sudo"exitAnd now the detail that fools everyone: go back to your user’s terminal, close the session with
exitand connect again. Linux only reads a user’s groups at the moment the session opens — yours was opened before the group existed, so it would keep refusing the command even with the fix already applied. Once reconnected, test withsudo whoami: it should answerroot.
Step 6 — Clone the repository
Section titled “Step 6 — Clone the repository”Clone into /opt and give ownership of the folder to your user:
sudo git clone --branch main https://github.com/kelvindk9w/tws-panel.git /opt/tws-panelsudo chown -R $USER:$USER /opt/tws-panelcd /opt/tws-panelStep 7 — Run the installer
Section titled “Step 7 — Run the installer”The installer installs Docker if needed, builds the image, brings the containers up
and finishes by showing your panel’s address, already with HTTPS:
https://YOUR-IP-WITH-HYPHENS.sslip.io (e.g. https://203-0-113-10.sslip.io). No tunnel and no
choosing a port.
./scripts/install.sh🩺 Pre-flight: what shows on screen and what to do in each scenario
Scenario 1 — clean VPS (the expected case): the report comes out all green and the installation continues on its own, without asking for anything. (The installer prints in Portuguese for now. In short: the machine is being inspected without changes, then OS, RAM, free disk, Docker, running containers and ports are listed, and it ends with “clean machine detected — proceeding with the installation”.)
[tws-panel] Pré-flight: inspecionando a máquina (nada será alterado nesta etapa)… ✓ SO: Ubuntu 24.04.2 LTS (suportado) ✓ RAM: 1984 MB ✓ Disco livre em /: 23 GB ✓ Docker: ausente (será instalado por este script) ✓ Nenhum container Docker em execução ✓ Portas do proxy e do e-mail (80/443/25/465/587/143/993/8080) livres[tws-panel] Máquina limpa detectada ✓ — prosseguindo com a instalação.You don’t need to do anything — just wait for the build (it takes a few minutes the first time).
Scenario 2 — VPS already in use: the report shows items with ⚠ and the installer stops and
waits for your decision. (In short: the installer lists the ports and services it found, warns that the VPS does not look clean, says it never removes or stops anything, and asks you to type “continuar” to proceed anyway.)
⚠ Portas em uso: 80 443 ⚠ Serviço ativo: nginx
================================================================================ ⚠️ ATENÇÃO: esta VPS NÃO parece estar limpa (2 ponto(s) acima).
O TWS Panel foi feito para uma VPS Ubuntu LIMPA. Continuar pode causar conflitos (portas, serviços, recursos) com o que já existe na máquina. Este instalador NUNCA remove ou para nada que já exista — mas os serviços do painel podem falhar ao subir se as portas estiverem ocupadas.
Para prosseguir mesmo assim, digite "continuar" — ou rode com --force (PAAS_FORCE=1) em automações.================================================================================
Digite "continuar" para prosseguir:What to do:
- Recommended: press Ctrl+C (or simply type nothing and close it) to abort — nothing was installed or changed. Resolve the conflicts (e.g. disable nginx if it is no longer used, or get a clean VPS) and run the installer again.
- If you know what you’re doing (e.g. the listed service doesn’t use the panel’s ports):
type
continuarand press Enter. By typing it, you confirm that you read the report and accept the risk of conflicts.
Common errors:
- Typed it wrong (
continua,Continuar) — the installer aborts withInstalação abortada. Nada foi instalado ou alterado.(Portuguese for “Installation aborted. Nothing was installed or changed.”). Just run it again. - Don’t use
--forceon your first try — it skips exactly the reflection this warning is meant to provoke.--forceexists for automation, not for hurrying.
Right after the pre-flight, the installer asks two questions. They decide how the panel’s live terminal works — it is where the security scan and the hardening run:
- Which user the terminal opens as. It lists the VPS users that have administrator
permission, but you are the one who types the name — even if there is only one. Type the user
you created in Step 3.
rootis also accepted, but we don’t recommend it: a forgotten browser tab left open would give root access to the VPS to whoever sat in front of it. - How to run what needs root. Answer
1(senha, i.e. password) — it’s the recommended one: when something needs administrator rights, the panel usessudoinside the terminal and you type your password there, as you would over SSH. The other option,2(segundo-plano, i.e. background), doesn’t ask for a password: those commands run as root behind the scenes, and you check them afterwards on the Audit log screen.
If the user won’t do (doesn’t exist, has no password, has no sudo…), the installer explains what
to do and asks again. Nothing is installed before you answer. The panel’s port is chosen
by the installer itself: it is internal only, you will never type it.
🔑 Password or background? What happens in each, no fine print
senha (password) mode (recommended). The terminal opens as your user. When a scan or a hardening
phase needs root, the panel runs the command with sudo in the terminal itself, and sudo
asks for your password — the same one from adduser. Nothing runs as administrator without you seeing and authorizing it.
- The trade-off: the password leaves your browser, passes through the panel and reaches the VPS terminal. It is not saved, logged or sent anywhere — the project is open source and this can be verified in the code. On the way between your browser and the VPS it goes encrypted: the panel is opened over HTTPS (or through the SSH tunnel, if you choose that access).
- Requires that the user has a password and is in the
sudogroup — which Step 3 already did.
segundo-plano (background) mode. The terminal also opens as your user, but the commands that need
root run as root, behind the scenes, through the same bridge the panel already uses to talk to the VPS. The
output shows up in the terminal just so you can follow along.
- The trade-off: it doesn’t ask for a password, so you don’t authorize command by command. The check comes afterwards: everything is recorded on the panel’s Audit log screen.
- Doesn’t require
sudo— you can even use a user created just for the panel. The installer warns if the chosen user doesn’t havesudo, because then, signing in over SSH with it, you can’t do anything as administrator.
In both modes, the scheduled automatic monitoring — which runs by itself, with nobody to type a password — keeps running as root in the background, and each run is recorded in the Audit screen.
Condition for both modes to really protect you: the terminal user must not be in the
docker group. Anyone in that group becomes root without a password with a single command, so they would bypass
both the password prompt and the Audit. If the user is in it, the installer warns and offers to
remove them — see Why the panel doesn’t put you in the docker group.
Installing through automation (with nobody to answer)? Pass the choices directly:
./scripts/install.sh --terminal-user=YOUR_USER --root-mode=senhaWithout these options, with --force or without an interactive terminal, the installer doesn’t guess: it keeps the
old behavior (terminal as root) and warns. On a reinstall, what you chose the
first time is kept without asking again — to change it, see
Changing the panel’s terminal user.
🔐 Optional — access only through an SSH tunnel (the panel stays invisible on the internet)
By default the panel opens at https://YOUR-IP-WITH-HYPHENS.sslip.io. If you prefer it to have no
address at all on the internet — only people with SSH to the VPS reach it —, install with:
./scripts/install.sh --acesso=tunelIn this mode the installer asks which VPS port the panel sits on and finishes by showing the SSH
tunnel command and the address http://localhost:PORT/?token=…. The browser will show “Not secure”
because it is http://localhost — and here that’s fine: the traffic travels encrypted inside the SSH
tunnel. To switch modes later, run the installer again with --acesso=https or --acesso=tunel.
With the panel on HTTPS, Phase 02 also closes the SSH tunnel (it isn’t used); when switching to
--acesso=tunel, the installer reopens the tunnel on its own.
Common errors when opening the tunnel:
bind [127.0.0.1]:9000: Address already in use(when opening the tunnel) — the port that’s taken is on your computer, not the VPS’s: the left number inssh -L. One of your programs is already using 9000 (common for people who program). Change only that number and open the browser on it:ssh -L 9100:localhost:9000 YOUR_USER@YOUR_IP, thenhttp://localhost:9100/?token=.... Any high free number will do — 9100, 9300, 12345. Understand the two ports of the tunnel. On the next installation, this error won’t even show up if you check beforehand which port is free on your computer and give that number to the installer.- “This site can’t be reached” in the browser, with the tunnel open — the address has the
wrong number. What goes in the browser is always the left number of your
ssh -L, which is your computer’s port. If your tunnel isssh -L 9100:localhost:9001, the address ishttp://localhost:9100/?token=...—9001is the VPS’s port and never appears in the browser. Check which number your tunnel is listening on withss -ltnH 'sport = :9100'(Linux/WSL). channel 2: open failed: connect failed: Connection refused(the tunnel opens, but the browser says it couldn’t connect) — that’s the other side: the right number doesn’t match the port the panel is on in the VPS. Check withsudo ./scripts/show-token.sh, which prints the command already with the right port.
Step 8 — Open the panel
Section titled “Step 8 — Open the panel”Open the panel at the https://…sslip.io address the installer showed at the end (it already
carries the setup token in the link; if you need it, the token also appears highlighted in the terminal) and follow
the wizard. The wizard opens in the language of your browser (see Language of the interface below).
┌──────────────────────────────────────────────────────────────────────┐│ Setup wizard — https://YOUR-IP.sslip.io/?token=… │├──────────────────────────────────────────────────────────────────────┤│ 1. Welcome and token → validates the setup token and unlocks ││ the server's live terminal ││ 2. Server health → CPU, RAM, disk, network — the checks ││ run LIVE in the embedded terminal ││ 3. Security → Lynis scan → plan → hardening ││ (with automatic rollback in 5 min) ││ 4. Administrator account → the panel's username + strong password │└──────────────────────────────────────────────────────────────────────┘
Language of the interface. The panel is available in English and Portuguese. On the first visit
it goes through your browser’s list of languages and uses the first one it has: Portuguese for any
pt variant (such as pt-BR), English for en; if none matches, English. To change it, use the Language selector on the setup wizard and sign-in
screens, in the panel header (next to the user menu, always visible once you are signed in; with the side menu layout, at the bottom of the sidebar, above the user menu), or under Settings → Appearance. The choice is saved in that
browser only (it is not part of your account), so another browser or device starts again from its
own language. The messages that the server itself writes (see the note at the top of this page) are
still in Portuguese.
The Security step is the one that demands the most attention: it changes how you sign in to the VPS. Before applying the phases, read the Security wizard.
The installer is idempotent: it can be run again without breaking anything (rebuild + restart).
The panel runs 100% in Docker (docker compose up -d), with state persisted in the
paas_data volume and access to the Docker socket to manage Caddy, Stalwart and your projects.
🎫 The installer's final banner — and how to recover the token later
When the installation finishes, the terminal plays a “beep” and shows a banner like this. (This is real installer output, in Portuguese. In short: “TWS Panel installed and running successfully”, the link to open in the browser, a note that the connection uses HTTPS, and the setup token box. The token is highlighted only now, so keep it until you finish the wizard; it stops working after you create the admin account.)
████████████████████████████████████████████████████████████████████████████████ ████ ✅ TWS PANEL INSTALADO E RODANDO COM SUCESSO! ████ ████████████████████████████████████████████████████████████████████████████████
👉 PRÓXIMO PASSO: abra o painel no navegador
Abra no navegador (de qualquer computador — não precisa de túnel nem de porta):
https://203-0-113-10.sslip.io/?token=<seu-token-de-48-caracteres>
A conexão é criptografada (HTTPS, certificado Let's Encrypt): a senha de administrador que você vai criar e a senha do sudo, se o terminal pedir, trafegam protegidas. O sslip.io só traduz o nome para o IP desta VPS — o tráfego vai direto do seu navegador para cá.
┌──────────────────────────────────────────────────────────────────────────┐│ ⚑ SETUP TOKEN ⚑ ││ ││ <seu-token-de-48-caracteres> ││ ││ ⚠ Ele aparece SÓ AGORA em destaque. Guarde-o até concluir o wizard. ││ ⚠ Após criar a conta admin no fim do wizard, ele é invalidado. │└──────────────────────────────────────────────────────────────────────────┘(Where the sample shows <seu-token-de-48-caracteres>, the real banner shows your actual 48-character token.)
What to do: copy the full link (https://…sslip.io/?token=...) and paste it into the browser.
It already carries the token embedded — you don’t need to type anything. Installed with --acesso=tunel? The banner
shows, in its place, the tunnel command and the http://localhost:… address (see
SSH tunnel access).
Closed the terminal and lost the banner? Don’t panic. On the VPS, run either one:
sudo ./scripts/show-token.sh # shows the full link + token againsudo docker exec tws-panel cat /data/setup-token # shows only the tokenCommon errors:
- The installer said the
https://…sslip.ioaddress didn’t respond within 3 minutes — the certificate wasn’t issued. The most common cause is ports 80 and 443 blocked by a provider firewall (in the hosting panel): open both and restart the panel withsudo docker compose restart. To see what the proxy says:sudo docker logs paas-caddy --tail 50. In the meantime, tunnel access works (the banner shows the command). - Errors when opening the tunnel (only with tunnel access) — they are explained in SSH tunnel access.
permission denied while trying to connect to the Docker daemon socket— you forgotsudoin front of the command. This is on purpose: the panel doesn’t put anyone in the docker group (see the reason).setup token não encontrado...(Portuguese for “setup token not found…”) — eithershow-token.shwas run on a machine without the panel (run it on the right VPS, from inside/opt/tws-panel), or the wizard was already completed and the token was deleted on purpose: to reopen it with a new token,sudo ./scripts/reset-setup.sh.- The token no longer works in the browser — after you create the admin account (the last step of the wizard), the token is invalidated forever. From then on, access is through the sign-in screen, with your panel username and password.
After the wizard
Section titled “After the wizard”On first access, the Dashboard shows the Get the panel ready checklist, with what is still left to configure (protections, two-factor authentication, panel domain, email, notifications and backups). Part 2 walks through each of them, in order.
To publish a project: register the project, point the DNS, and the panel takes care of the build, the proxy and SSL. Production guide in production.md. Everyday commands, the projects folder and changing the terminal’s port or user are in Operation.