V4call-v0.11
| Please Select a Licence from the LICENCE_HEADERS page |
And place at top of your page |
If no Licence is Selected/Appended, Default will be CC0 Default Licence IF there is no Licence placed below this notice!
When you edit this page, you agree to release your contribution under the CC0 Licence LICENCE:
More information about the cc0 licence can be found here: You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. Licence: Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; moral rights retained by the original author(s) and/or performer(s); publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; rights protecting the extraction, dissemination, use and reuse of data in a Work; database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. |
|
LICENCE: When you edit this page, you agree to release your contribution under the MIT Licence LICENCE Copyright <YEAR> <COPYRIGHT HOLDER> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
v4call — Deploy Your Own Server on Ubuntu 24.04 with Docker
From CompleteNoobs
This guide walks through deploying your own v4call server from scratch on a fresh Vultr Ubuntu 24.04 VPS — from first login to a working HTTPS video/audio calling service on your own domain, optionally federated with other v4call servers.
v4call is an open-source, decentralised video and audio calling platform that uses Hive blockchain for identity and HBD micropayments. It supports custom Hive-Engine token payments, encrypted direct messages with persistent chat history, voice-only and video calls, and a free-market platform fee system. Fork it, run your own server, keep all your platform fees, join the federation.
Source code: https://github.com/CompleteNoobs/v4call
End result: A working v4call server at https://call.yourdomain.com that you can log into with a Hive account, optionally connected to other v4call servers via federation.
Contents
- 1 What You Need
- 2 Step 1: Create Your Vultr VPS
- 3 Step 2: Point Your Domain at the VPS
- 4 Step 3: Log into Your VPS
- 5 Step 4: Update the Server
- 6 Step 5: Install Docker
- 7 Step 6: Install Git
- 8 Step 7: Fork and Clone the Code
- 9 Step 8: Configure Your Server (.env file)
- 10 Step 9: Configure Nginx — HTTP Only First
- 11 Step 10: Create Data Directories and Fix Permissions
- 12 Step 11: Build and Start the Server
- 13 Step 12: Get Your SSL Certificate
- 14 Step 13: Enable HTTPS in Nginx
- 15 Step 14: Set Up SSL Auto-Renewal
- 16 Step 15: Test Everything is Working
- 17 Step 16: Set Up Your Call Rates on Hive
- 18 Step 17: Federation Setup (Optional)
- 19 Feature Guide: What Your Server Can Do
- 20 Admin Configuration Reference
- 21 Updating Your Server
- 22 Common Problems and Fixes
- 23 Quick Reference
What You Need
Before starting, make sure you have the following:
- A Vultr account — sign up at vultr.com - please use are Vultr Referral link to help us cover server costs.
- A domain name with DNS access — e.g.
call.yourdomain.com - Two Hive accounts — one for your server identity (receives platform fees), one for escrow (holds caller funds during calls). Create free accounts at signup.hive.io
- Hive Keychain browser extension — for login and payments. Install from hive-keychain.com
- A GitHub account — free at github.com. You will fork the v4call project.
- A terminal — Mac: Terminal app. Windows: PowerShell or PuTTY.
You do not need to know how to code. Every command can be copy-pasted exactly as shown.
Step 1: Create Your Vultr VPS
- Log into my.vultr.com
- Click Deploy New Server
- Choose Cloud Compute — Shared CPU
- Choose a location close to you
- Choose Ubuntu 24.04 LTS x64
- Choose the $6/month plan (1 CPU, 1GB RAM)
- Set Server Hostname to something like
v4call-server - Click Deploy Now
Wait ~60 seconds for it to start. Click the server to find:
- IP Address — looks like
123.456.789.012— write it down - Password — click the eye icon — write it down
Step 2: Point Your Domain at the VPS
Log into your DNS provider and add an A record:
| Field | Value |
|---|---|
| Type | A |
| Name | call (or @ for root domain)
|
| Value | Your VPS IP address |
| TTL | 300 |
DNS takes a few minutes to propagate. Check from your computer later:
nslookup call.yourdomain.com
Must show your VPS IP before Step 12. You can continue with all other steps while waiting.
Step 3: Log into Your VPS
Open a terminal on your computer:
ssh root@YOUR_SERVER_IP
- NOTE: you can also do
root@your-domain.com
Type yes when asked about the fingerprint, then paste the password from Vultr (right-click to paste in most terminals).
When you see root@v4call-server:~# you are in.
Step 4: Update the Server
apt update && apt upgrade -y
Wait for it to complete (1-2 minutes).
Step 5: Install Docker
Install Docker using the official installer script:
curl -fsSL https://get.docker.com | sh
Verify:
docker --version
Should show Docker version 26.x.x or similar.
Install Docker Compose plugin:
apt install -y docker-compose-plugin
Verify:
docker compose version
Should show Docker Compose version v2.x.x.
Step 6: Install Git
apt install -y git git --version
Step 7: Fork and Clone the Code
Clone onto your VPS
cd /opt git clone https://github.com/CompleteNoobs/v4call.git cd v4call
Fork on GitHub - Optional
Forking gives you your own copy of the code that you can customise — change the name, branding, fees — without affecting the original project.
- Go to https://github.com/CompleteNoobs/v4call
- Click the Fork button (top right of the page)
- Select your GitHub account as the destination
- Click Create fork
You now have your own copy at https://github.com/YOURUSERNAME/v4call
Clone onto your VPS
cd /opt git clone https://github.com/YOURUSERNAME/v4call.git cd v4call
List the files to confirm:
ls
You should see: server.js public/ Dockerfile docker-compose.yml nginx/ package.json .env.example README.md
Worth knowing about the public/ folder — alongside the main UI (index.html) you'll find:
rate-editor.html— generates your rates post for Hiveserver-sign.html— generates the signed federation verify file (used in Federation Setup below)server-announce.html— publishes your server to the on-chain federation directoryadmin-peers.html— manages your federation peer listinfo.html— public landing page for visitors who haven't logged in.well-known/v4call-server.json— placeholder; each operator overwrites this with their own signed file in Federation Setup
Step 8: Configure Your Server (.env file)
All settings live in a single .env file. Copy the template:
cp .env.example .env nano .env
Fill in your values:
# ── Server Identity ────────────────────────────────────── SERVER_NAME=yourcallapp SERVER_DOMAIN=call.yourdomain.com SERVER_HIVE_ACCOUNT=yourhiveaccount ESCROW_ACCOUNT=yourescrowaccount V4CALL_ESCROW_KEY=5Kxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Secret key to access /admin/* endpoints. # Choose a long random string — treat it like a password. # Example: openssl rand -hex 32 ADMIN_KEY=make-up-a-long-random-string-at-least-20-characters # ── Hive Blockchain ────────────────────────────────────── CHAIN=hive HIVE_API= # ── Platform Fee ───────────────────────────────────────── # Percentage your server takes from each paid call/DM (10 = 10%) # This is the MINIMUM fee — users whose rates post sets a lower # platform fee will be rejected. Users who set a higher fee # get the best price (your server's rate, not their higher number). DEFAULT_PLATFORM_FEE=10 # ── Network ────────────────────────────────────────────── PORT=3000 BIND_HOST=127.0.0.1 # ── Chat Storage ───────────────────────────────────────── # How many days to keep stored DMs before automatic cleanup DM_RETENTION_DAYS=33 # How many days to keep stored room messages before cleanup ROOM_RETENTION_DAYS=33 # How many recent DMs per conversation to show on login (0 = off) DM_PREVIEW_COUNT=1 # ── Call Behaviour (advanced — defaults are fine) ──────── # CALL_COOLDOWN_MS=30000 # MAX_CALL_DURATION_MIN=120 # PAYMENT_VERIFY_RETRIES=3 # PAYMENT_VERIFY_DELAY_MS=5000 # ── Federation (optional) ──────────────────────────────── # Comma-separated list of peer v4call server federation WebSocket URLs. # Leave blank for standalone mode (no federation). # Servers listed here are auto-approved on startup. Discovered peers # (via Hive directory) need manual approval at /admin-peers.html. # Example two-server setup: # On call.completenoobs.com → FEDERATION_PEERS=wss://hive-book.com/federation # On hive-book.com → FEDERATION_PEERS=wss://call.completenoobs.com/federation FEDERATION_PEERS=
Key points:
V4CALL_ESCROW_KEY— must be the active private key for your escrow account. Find it in your Hive wallet → Keys & Permissions → Active. Starts with5K. Never share this.ADMIN_KEY— invent a secret password for accessing admin toolsHIVE_API— leave blank to use all built-in Hive nodes automaticallyDEFAULT_PLATFORM_FEE— your server's minimum platform fee percentage. See Platform Fee System below for how this works.DM_RETENTION_DAYS/ROOM_RETENTION_DAYS— how long chat messages are kept in the database. A cleanup job runs every hour and deletes anything older. Set to0to keep messages indefinitely (not recommended).DM_PREVIEW_COUNT— when a user logs in, this many recent DMs per conversation are loaded into the lobby chat so they can see previews. Set to0to disable previews (users still get an unread count alert).FEDERATION_PEERS— leave blank for now. You can add peers later (see Federation Setup).
Save: Ctrl+X → Y → Enter
Step 9: Configure Nginx — HTTP Only First
This step is critical. A very common mistake is putting the HTTPS/SSL config in Nginx before getting the certificate. Nginx tries to load the certificate at startup — if the file doesn't exist yet, Nginx crashes in a restart loop. Certbot then cannot serve the challenge because Nginx is down. Result: no certificate, stuck in a loop.
The fix: always start with HTTP only, get the certificate, then add HTTPS.
Edit the Nginx config:
nano /opt/v4call/nginx/v4call.conf
Delete everything and replace with this HTTP-only config:
server {
listen 80;
server_name call.yourdomain.com www.call.yourdomain.com;
# Certbot challenge path — do not remove this block
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Proxy all other requests to the v4call app
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
Replace call.yourdomain.com with your actual domain in both places.
Save: Ctrl+X → Y → Enter
Step 10: Create Data Directories and Fix Permissions
Create the folders Docker uses for persistent data:
mkdir -p /opt/v4call/data/logs \ mkdir -p /opt/v4call/data/certbot/conf \ mkdir -p /opt/v4call/data/certbot/www/.well-known/acme-challenge \ mkdir -p /opt/v4call/data/certbot/logs
Fix permissions — do not skip this.
The v4call app runs inside Docker as user node (UID 1000). On the host, these directories are created by root, so the container cannot write to them. This causes a SQLITE_CANTOPEN error that crashes the app.
Fix the logs directory for the app:
chown -R 1000:1000 /opt/v4call/data/logs
Certbot runs as root so its directories stay root-owned:
chown -R root:root /opt/v4call/data/certbot chmod -R 755 /opt/v4call/data/certbot
Note: The app creates two separate SQLite databases in the logs directory:
v4call-ledger.db— payment records (calls, ring fees, payouts). Only the server writes to this.v4call-chat.db— stored DMs and room messages. Separate from the ledger for security — if a bug in chat storage were exploited, the payment ledger remains untouched.
Federation also persists state in this directory:
approved-peers.json— list of federation peer domains you've approved (created on first approval; survives container restarts).
Step 11: Build and Start the Server
cd /opt/v4call docker compose up -d --build
The first build downloads dependencies and takes 2-4 minutes. Check the status:
docker compose ps
You should see:
NAME STATUS v4call-app Up (healthy) v4call-nginx Up
Check the app started correctly:
docker compose logs app
Look for:
[ledger] SQLite ready: /app/logs/v4call-ledger.db [chat] SQLite ready: /app/logs/v4call-chat.db v4call server running on 0.0.0.0:3000 [config] Server: yourcallapp (call.yourdomain.com) [config] DM retention: 33 days | Room retention: 33 days | DM preview: 1 ✓ Escrow key verified — matches @yourescrowaccount active key ✓ Escrow account @yourescrowaccount balance: 0.000 HBD
If you see SqliteError: unable to open database file — run the chown command from Step 10 again then docker compose restart app.
Test HTTP is working:
curl http://call.yourdomain.com/debug-state
Should return: {"lobbyUsers":[],"rooms":[]}
If you can see the site in a browser over HTTP at this point — everything is working and ready for the certificate.
Step 12: Get Your SSL Certificate
Before running certbot, verify DNS and the challenge path are both working:
# Check DNS points to this server nslookup call.yourdomain.com
# Test the challenge path echo "test" > /opt/v4call/data/certbot/www/.well-known/acme-challenge/testfile curl http://call.yourdomain.com/.well-known/acme-challenge/testfile
The curl command must return test. If it does not, Nginx is not running — check docker compose ps and docker compose logs nginx.
When both work, get the certificate. The --entrypoint certbot flag is required — without it, Docker runs the container's default renewal loop instead of the certonly command:
docker compose run --rm \ --entrypoint certbot \ certbot certonly \ --webroot \ -w /var/www/certbot \ -d call.yourdomain.com \ --email your@email.com \ --agree-tos \ --no-eff-email
Replace call.yourdomain.com with your domain and your@email.com with your email.
Success looks like:
Requesting a certificate for call.yourdomain.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/call.yourdomain.com/privkey.pem This certificate expires on 2026-07-09.
Verify the files exist on the host:
ls /opt/v4call/data/certbot/conf/live/call.yourdomain.com/
Should show: cert.pem chain.pem fullchain.pem privkey.pem README
Step 13: Enable HTTPS in Nginx
Now the certificate exists, update Nginx to serve HTTPS.
nano /opt/v4call/nginx/v4call.conf
Replace everything with the full HTTPS config:
# HTTP — only used for certbot challenge and redirect to HTTPS
server {
listen 80;
server_name call.yourdomain.com www.call.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS — main app
server {
listen 443 ssl;
server_name call.yourdomain.com www.call.yourdomain.com;
# CHANGE PATH: ssl certs path uses your domain — find with: ls data/certbot/conf/live/
ssl_certificate /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/call.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# When user cancels the basic-auth login prompt, send them here
error_page 401 /info.html;
# info.html is served directly by Nginx — no auth, no proxy
location = /info.html {
root /usr/share/nginx/html;
auth_basic off;
}
# WebSocket — main client traffic
location /socket.io/ {
# Uncomment below to require username/password to enter site (testing only).
# See "Optional: Password Protect Your Server During Testing" section for setup.
#auth_basic "v4call — Private Testing";
#auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# Federation WebSocket — server-to-server, NEVER auth-protected.
# Peer v4call servers connect here to exchange presence/DMs/call invites.
# Trust validation happens inside the Node app (signed verify.json check
# + operator approval via /admin-peers.html). Do not add basic auth here —
# it would block all federation.
location /federation {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# Everything else — main HTTP traffic
location / {
# Uncomment below to require username/password to enter site (testing only).
#auth_basic "v4call — Private Testing";
#auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
proxy_send_timeout 300;
}
# Logs failed and successful login attempts when .htpasswd is enabled
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log;
}
- REPLACE
call.yourdomain.comwith your domain throughout (it appears 4 times — server_name lines and ssl_certificate paths). - The
auth_basiclines are commented out by default so federation and normal users work out-of-box. To enable password protection for private testing, see the Optional section below — it shows how to uncomment them and create users. - Never add
auth_basicto the/federationblock — it would block all peer v4call servers from connecting to you.
Save and restart Nginx:
docker compose restart nginx
Check the logs — you should see no [emerg] errors:
docker compose logs nginx
The last lines should show:
nginx/1.x.x ... start worker processes
- If you see an error such as:
[emerg] 1#1: cannot load certificate "/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem, r) error:10000080:BIO routines::no such file)
- Make sure you edited
nginx/v4call.confcorrectly. - To find the path for your SSL certs look in path
data/certbot/conf/live/ - There are 4 lines to check in
nginx/v4call.conf:server_name call.yourdomain.com www.call.yourdomain.com;(HTTP block)server_name call.yourdomain.com www.call.yourdomain.com;(HTTPS block)ssl_certificate /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/call.yourdomain.com/privkey.pem;
- When done restart nginx with:
docker compose restart nginx
Step 14: Set Up SSL Auto-Renewal
Let's Encrypt certificates expire after 90 days. A cron job renews them automatically.
crontab -e
Add this line at the bottom:
0 3 * * * cd /opt/v4call && docker compose run --rm --entrypoint certbot certbot renew --quiet && docker compose exec nginx nginx -s reload
Save and exit. This runs at 3am every day, renews if the cert is close to expiry, and reloads Nginx to pick up the new certificate.
Step 15: Test Everything is Working
Test HTTPS:
curl https://call.yourdomain.com/debug-state
Should return: {"lobbyUsers":[],"rooms":[]}
Test admin access:
curl "https://call.yourdomain.com/admin/balance?key=YOUR_ADMIN_KEY"
Should return your escrow account balance.
Open your browser and go to https://call.yourdomain.com
You should see the v4call login page with:
- A padlock icon in the browser address bar
- A green ⚡ Sign in with Keychain button (if Hive Keychain is installed)
- A manual posting key login option below it
- A 📖 New here? Learn the v4call basics → link at the bottom
Log in with Hive Keychain or a posting key to confirm the login flow works. 🎉
Step 16: Set Up Your Call Rates on Hive
For callers to be charged when they ring you, publish your rates on the Hive blockchain:
- Make sure Hive Keychain is installed in your browser
- Go to
https://call.yourdomain.com/rate-editor.html - Enter your Hive username
- Set your rates — ring fee, connect fee, duration rate per hour, minimum credit deposit
- Set
PLATFORM-FEEto at least your server'sDEFAULT_PLATFORM_FEEpercentage (e.g.10for 10%). If you set it lower, paid contacts to your account will be rejected on this server. - Optionally add custom token sections (e.g.
[TOKEN:CNOOBS]) to offer discounted rates for callers who hold your token - Click Generate to preview the rates block
- Click Post to Hive — Keychain will ask you to approve the post
This creates a post titled v4call-rates on your Hive blog. Your server reads this post automatically.
To verify your server read the rates correctly:
https://call.yourdomain.com/debug-rates/yourusername
To test with a specific caller (checks their token balances too):
https://call.yourdomain.com/debug-rates/yourusername?caller=theirusername&type=voice
You should see your rates as JSON, including which currency and rates apply for that caller.
Step 17: Federation Setup (Optional)
If you want your server to talk to other v4call servers — so users on different servers can call/DM each other — set up federation. This is optional. A standalone server works fine without it.
Federation has two trust steps:
- Domain proof — you publish a signed JSON file at
https://yourdomain.com/.well-known/v4call-server.jsonproving that your Hive account controls this domain - Directory listing — you publish a Hive post tagged
v4call-serverannouncing your server exists, so other operators can discover you
Important escrow rule: the ESCROW_ACCOUNT in your .env must match the ESCROW: field in your users' rates posts. The active key for that escrow has to live on this server. If a user's rates post points at a different escrow than what your server holds the key for, paid calls/DMs to that user will fail (your server can't disburse from an escrow it doesn't control).
Step 17a: Generate and Host the Verify File
- Open
https://yourdomain.com/server-sign.htmlin a browser with Hive Keychain installed - Fill in: Hive account (same as
SERVER_HIVE_ACCOUNT), domain, escrow account, fee account, federation WS URL (wss://yourdomain.com/federation) - Click ⚡ Sign with Hive Keychain
- Click ⬇ Download — saves
v4call-server.json - Upload to your server, replacing the placeholder file. From your local machine:
scp v4call-server.json root@yourvps:/opt/v4call/public/.well-known/
(Or use any other transfer method you prefer — SFTP client, paste via SSH, etc.)
Verify it's served:
curl https://yourdomain.com/.well-known/v4call-server.json
You should see your signed JSON. If you see 404, check the file is at exactly public/.well-known/v4call-server.json — the directory uses a hyphen, not an underscore (RFC 8615), and the filename must be exactly v4call-server.json.
Rebuild so the file is baked into the Docker image:
docker compose down && docker compose build --no-cache && docker compose up -d
Tip: commit the generated file to your git repo so future fresh installs include it automatically. It contains only public data — safe to commit. The placeholder shipped in the repo is overwritten by your file.
Step 17b: Announce on Hive
For other operators to discover you, publish a Hive post advertising your server:
- After signing in Step 17a, click the 📡 Announce on Hive → link on the signer output card (auto-fills the announce page)
- Or open
https://yourdomain.com/server-announce.htmldirectly - Confirm the prefilled values
- Click 📡 Post to Hive — Keychain will broadcast the post
The post lives forever on Hive. If your config changes, just post a new one — the most recent post per Hive account wins. No need to delete old posts (Hive posts are effectively permanent anyway).
Step 17c: Connect to Known Peers (Manual Mode)
Skip the discovery step and go straight to a known peer by adding their WebSocket URL to FEDERATION_PEERS in .env:
FEDERATION_PEERS=wss://otherserver.com/federation
Both servers must list each other. Rebuild after editing .env:
docker compose down && docker compose build --no-cache && docker compose up -d
Servers in FEDERATION_PEERS are auto-approved on startup — they bypass the manual approval step.
Step 17d: Discover and Approve Peers (Auto Mode)
Your server scans Hive for v4call-server posts on startup and every 2 hours. Discovered peers are NOT auto-connected — you must approve each one.
- Open
https://yourdomain.com/admin-peers.html - Paste your
ADMIN_KEY(from.env) and click 🔄 Load / Refresh - Each discovered peer shows a card with verification status, escrow, fee account, post age
- Click ✓ Approve to add them to your federation
- Click ✗ Revoke to remove an approved peer
Approvals persist to /app/logs/approved-peers.json so they survive container restarts.
Federation Health Check
Watch the federation logs:
docker compose logs -f app | grep -E "\[federation\]|\[discovery\]|\[peers\]"
Healthy startup looks like:
[federation] Approved peers: peer-domain.com [federation] Connecting to wss://peer-domain.com/federation... [federation] Outbound connected: wss://peer-domain.com/federation [federation] ✓ Peer verified: @peer-domain.com (signer: @peer-account, escrow: @peer-escrow) [discovery] Hive returned 2 post(s) under v4call-server tag [discovery] Scan complete — 2 v4call-server post(s), 2 verified
When a user logs in on either server, you should see:
[federation] → user-online @username → 1 peer(s) (on their home server) [federation] ← user-online @username@theirhome.com (on your server)
Feature Guide: What Your Server Can Do
This section explains all the features available in v4call and how they work. No code changes needed — everything described here is built in and ready to use.
Login Options
v4call supports two ways to sign in:
- Hive Keychain (recommended) — click the green ⚡ Sign in with Keychain button. Keychain signs a challenge to prove your identity. No key paste needed. After login, a 🔑 panel appears in the lobby where you can optionally enter your posting key to unlock encrypted messaging (Keychain cannot expose private keys, so encryption needs the key entered once per session).
- Manual posting key — paste your Hive posting private key (starts with
5J) directly. The key stays in browser session memory only — never sent to the server.
Both methods verify your identity against the Hive blockchain.
Voice and Video Calls
Each online user in the lobby shows three action buttons:
- 📞 Green phone icon — start a voice-only call (audio, no camera)
- 🎥 Blue camera icon — start a video call (audio + camera)
- 💬 Purple chat bubble icon — open the DM panel to send a direct message
Voice calls request microphone only — no camera permission prompt. Video calls request both. The caller and callee can have different call types — the type is set by whoever initiates the call.
Separate rates can be set for voice and video calls in the rates post (voice is typically cheaper).
Federated users (on a different v4call server) appear in the lobby with a small server-domain badge under their username. The call/DM buttons work the same — federation routing is transparent.
Direct Messages (DMs)
Click the purple 💬 button next to any online user to open the DM panel. DMs are end-to-end encrypted using Hive posting keys — the server stores only ciphertext it cannot read.
Chat storage: DMs are stored on the server in an encrypted database (v4call-chat.db) for up to DM_RETENTION_DAYS (default: 33 days). Both sender and recipient get their own encrypted copy stored, so both can retrieve their history later.
Unread alerts: When you log in, if you have unread DMs, a popup appears showing how many messages from how many users. Click a username in the popup to open their DM history.
DM previews: The last DM_PREVIEW_COUNT messages per conversation are loaded into the lobby chat on login, so you can see recent activity at a glance. Set to 0 in .env to disable.
Full history: Click the DM button for any user to load the complete conversation history, shown between "— DM history —" dividers.
Rooms
Users can create private rooms by selecting users in the lobby (toggle switch) and clicking Create & Invite. Rooms support encrypted messaging, video, and voice.
Room history: When a new user joins a room, they see past messages — broadcasts in full, encrypted messages only if they were addressed to them. A "— earlier messages —" divider separates history from live messages.
Ephemeral rooms: A warning banner at the top of every room says: "⚠ Room is ephemeral — if all users leave, the room and its history are deleted. New members can only read messages encrypted to their key." When the last person leaves a room, all stored messages for that room are deleted from the database.
Custom Token Payments (Hive-Engine)
v4call supports payment in any Hive-Engine token, not just HBD. This is configured per-user in their rates post using [TOKEN:SYMBOL] sections.
How it works:
- A user (e.g. @cnoobz) creates a custom token on Hive-Engine (e.g. CNOOBS)
- In their rates post, they add a
[TOKEN:CNOOBS]section with lower rates than their default HBD rates - When a caller who holds CNOOBS contacts @cnoobz, the server detects the token balance and offers the token rates
- If the caller holds multiple qualifying tokens, all options are shown in a currency picker — the caller chooses which to pay with
- The payment goes through Hive Keychain as a
custom_jsonHive-Engine transfer
For the escrow account: Your escrow account needs to hold some of each custom token that users on your server accept. Token payouts (to the callee) and refunds (to the caller) are sent from the escrow account's token balance, just like HBD.
Payment picker: When multiple payment options exist (e.g. CNOOBS at 1 per message, HBD at 100000 per message), the payment modal shows clickable currency buttons so the caller can see all rates and choose the best option.
Platform Fee System
The platform fee is how your server earns revenue from paid calls and DMs.
How it works:
- Your server's
DEFAULT_PLATFORM_FEEin.envis the minimum percentage your server accepts (e.g.10= 10%) - Each user sets
PLATFORM-FEEin their Hive rates post — this is the maximum fee they are willing to pay to a server - If the user's posted fee is lower than your server's minimum → rejected. The caller sees a message explaining the mismatch, and the callee is told to raise their fee.
- If the user's posted fee meets or exceeds your server's minimum → accepted, and the server charges its own rate (the minimum), not the user's higher number. The callee gets the best price.
- If the user has no
PLATFORM-FEEline in their rates post → the server's default is used automatically. No mismatch. - Free contacts (no payment involved) are never affected by fee enforcement.
Why this matters for federation: Different servers can set different platform fees. Users can shop around — pick a server with a fee they find agreeable. This creates a free market for server operators.
Example:
- Your server:
DEFAULT_PLATFORM_FEE=3(3%) - @alice posts:
PLATFORM-FEE: 5%→ accepted, server charges 3% (best price for alice) - @bob posts:
PLATFORM-FEE: 1%→ rejected, bob needs to raise to at least 3% - @charlie has no fee line → defaults to 3%, automatically accepted
Federation
Federation lets users on different v4call servers see, call, and DM each other. Each server independently:
- Holds its own escrow account (the active key never leaves the server it belongs to)
- Verifies and disburses payments through its own escrow
- Takes the platform fee for calls/DMs received by its own users
Cross-server payments work like this: caller pays the callee's escrow on Hive (read from the callee's rates post), the callee's server verifies the payment on-chain and forwards a notification, both servers exchange call/DM signalling, and the callee's server disburses callee-net + platform-fee from its own escrow at call end (with refunds going cross-server back to the caller as a normal Hive transfer).
WebRTC media stays peer-to-peer — neither server sees the audio/video.
See Step 17: Federation Setup for the setup steps.
Admin Configuration Reference
All settings are in the .env file. After changing any value, rebuild:
docker compose down && docker compose build --no-cache && docker compose up -d
| Variable | Default | Description |
|---|---|---|
SERVER_NAME |
v4call |
Display name for your server |
SERVER_DOMAIN |
v4call.com |
Your server's domain |
SERVER_HIVE_ACCOUNT |
v4call |
Hive account that receives platform fees (and signs your federation verify file) |
ESCROW_ACCOUNT |
v4call-escrow |
Hive account that holds funds during calls. The active key for this account must live on this server. |
V4CALL_ESCROW_KEY |
(none) | Active private key for the escrow account. Required. |
ADMIN_KEY |
(none) | Password for admin endpoints (/admin/balance, /admin/ledger, /admin/peers)
|
DEFAULT_PLATFORM_FEE |
10 |
Server's minimum platform fee percentage |
FEDERATION_PEERS |
(blank) | Comma-separated peer WebSocket URLs (e.g. wss://peer.com/federation). Listed peers are auto-approved on startup. Blank = standalone mode.
|
DM_RETENTION_DAYS |
33 |
Days to keep stored DMs before cleanup |
ROOM_RETENTION_DAYS |
33 |
Days to keep stored room messages before cleanup |
DM_PREVIEW_COUNT |
1 |
Recent DMs per conversation shown on login (0 = off) |
HIVE_API |
(blank) | Override primary Hive API node. Blank = auto-select from built-in list |
MAX_CALL_DURATION_MIN |
120 |
Maximum call length in minutes before auto-disconnect |
CALL_COOLDOWN_MS |
30000 |
Milliseconds between call attempts to same user |
PAYMENT_VERIFY_RETRIES |
3 |
Number of attempts to verify a blockchain payment |
PAYMENT_VERIFY_DELAY_MS |
5000 |
Delay between verification retry attempts |
Debug & Admin Endpoints
These are useful for testing without making actual calls:
/debug-state— shows current lobby users and active rooms (no auth required)/debug-rates/USERNAME— shows parsed rates for a user/debug-rates/USERNAME?caller=CALLER&type=voice— shows what rates a specific caller would receive (checks token balances too)/admin/balance?key=YOUR_ADMIN_KEY— shows escrow account HBD balance/admin/ledger?key=YOUR_ADMIN_KEY— shows recent payment records/admin/peers?key=YOUR_ADMIN_KEY— JSON list of discovered + approved federation peers/admin/peers/approve?key=YOUR_ADMIN_KEY&domain=peer.com(POST) — approve a discovered peer/admin/peers/revoke?key=YOUR_ADMIN_KEY&domain=peer.com(POST) — revoke approval/admin/peers/rescan?key=YOUR_ADMIN_KEY(POST) — force a Hive directory rescan
Operator Tool Pages
/rate-editor.html— generate yourv4call-ratesHive post/server-sign.html— generate your signed federation verify file/server-announce.html— publish your server announcement on Hive/admin-peers.html— UI for federation peer approve/revoke/rescan/info.html— public landing page (shown to users who hit the basic-auth login cancel)
Updating Your Server
Important: Always use docker compose down before rebuilding. Without this step, Docker may reuse the old container even after a rebuild, and your changes will not take effect.
cd /opt/v4call docker compose down docker compose build --no-cache docker compose up -d
Your data, SQLite databases and .env config are preserved — they live in data/logs/ which is a mounted volume.
To pull updates from GitHub and deploy:
cd /opt/v4call git pull docker compose down docker compose build --no-cache docker compose up -d
To push your own customisations to GitHub:
# On your local computer after making changes: git add . git commit -m "describe what you changed" git push
# On the VPS: cd /opt/v4call git pull docker compose down docker compose build --no-cache docker compose up -d
Common Problems and Fixes
Changes not showing after rebuild
If you edited server.js or index.html but changes are not visible, you probably forgot to bring Docker down first. docker compose restart and even docker compose up -d --build can reuse old containers.
Fix:
docker compose down docker compose build --no-cache docker compose up -d
SqliteError: unable to open database file
The app container runs as UID 1000 but the logs directory was created by root. Fix:
chown -R 1000:1000 /opt/v4call/data/logs docker compose restart app
Certbot says "No renewals were attempted"
The certbot container's default behaviour is a renewal loop. Your certonly command is being ignored. Always use --entrypoint certbot to override it:
docker compose run --rm --entrypoint certbot certbot certonly ...
Without --entrypoint certbot the container runs its renewal script instead of your command.
Nginx crashes with "cannot load certificate: No such file"
You added the HTTPS server block before getting the certificate. Nginx reads all server blocks at startup — if the cert file doesn't exist, the entire process fails.
Fix: revert nginx config to HTTP-only (Step 9), restart Nginx, get the certificate (Step 12), then re-add HTTPS (Step 13).
Webroot challenge test returns nothing
Nginx is not running. Check:
docker compose ps docker compose logs nginx
If Nginx is restarting — it has the HTTPS config with a missing cert file. Use the HTTP-only config.
"Escrow key does NOT match" warning on startup
The key in .env is the wrong type. You need the active private key, not the posting or owner key. Find it in your Hive wallet → Keys & Permissions → Active. It starts with 5K.
Site unreachable on port 443
Check that the certificate was issued:
ls /opt/v4call/data/certbot/conf/live/
Should show a folder with your domain name. If empty — go back to Step 12.
npm ci error during Docker build
The npm ci command requires a package-lock.json file. The project uses npm install instead. If you see this error, check your Dockerfile — it should say npm install not npm ci.
Custom token payments not working
If token rates are detected but payments fail:
- Check the escrow account holds the token — send some tokens to your escrow account on Hive-Engine
- Check the token symbol matches exactly (case-sensitive) between the rates post and Hive-Engine
- Check the server logs:
docker compose logs app | grep -i "token\|cnoobs\|escrow-token" - The Hive-Engine API endpoint must be
https://api.hive-engine.com/rpc/contracts— this is built into the code
[encrypted — unlock with 🔑 key panel to read]
You logged in with Hive Keychain. Keychain does not expose private keys, so encrypted messages cannot be decrypted without your posting key. Enter your posting key in the 🔑 panel at the bottom of the online users list. The key stays in browser session memory only — it is needed once per session.
Federation peer verification fails with HTTP 404
Logs show:
[federation] ✗ Peer verification failed for X: Cannot fetch verify.json: HTTP 404
The peer's /.well-known/v4call-server.json file isn't being served. Common causes:
- Wrong directory name — must be
.well-knownwith a hyphen, not.well_knownwith an underscore (RFC 8615). - Wrong filename — must be
v4call-server.json, notverify.jsonor anything else. The verifier code looks for this exact name. - Not in the Docker image — file lives at
public/.well-known/v4call-server.jsonin the repo and is copied into the image at build time. After placing the file, rebuild:docker compose down && docker compose build --no-cache && docker compose up -d. - Placeholder not replaced — the repo ships with a tiny placeholder. You must overwrite it with your own signed file from
/server-sign.htmlbefore federation will work.
Test from outside the container:
curl https://yourdomain.com/.well-known/v4call-server.json
Should return your signed JSON, not 404 or the placeholder.
Federation connection flapping (rapid disconnect/reconnect loop)
If logs show repeated Outbound connected followed by Disconnected — retry in 2s with no successful handshake, check verify.json:
- Is the file present at the path above?
- Does its
signaturematch the Hive account's posting key? Test by pasting the JSON into the Verify panel on/server-sign.html— should say "✓ Signature valid". - Does
hive_accountin the JSON matchSERVER_HIVE_ACCOUNTin the peer's .env? They must match.
Federation peer connects but my user can't be seen on the other side
If two servers connect successfully but a user on Server A doesn't appear in Server B's lobby, check:
- Server A logs:
[federation] → user-online @username → 1 peer(s)— confirms A broadcast the presence - Server B logs:
[federation] ← user-online @username@A.com— confirms B received it
If A logs the broadcast but B never received it, the federation socket might be one-way. Check both servers have approved each other (visible at /admin-peers.html).
Federated paid call/DM fails silently after payment
Most common cause: the user's v4call-rates post declares an ESCROW: account whose active key isn't on their home server. The caller's payment lands in that escrow correctly, but no server can disburse from it.
Fix: have the user re-post their rates with an ESCROW: matching their home server's ESCROW_ACCOUNT in .env. Or migrate that escrow account's active key to the right server.
The federation handshake also surfaces this as a clear error message via the escrow-mismatch guard — check the caller-side server logs for it.
Discovery scanner finds 0 posts
If /admin-peers.html shows "No v4call-server posts discovered yet" even after rescanning:
- Click 🔍 Rescan Hive now and watch logs:
docker compose logs -f app | grep discovery - Expected:
[discovery] Hive returned N post(s) under v4call-server tag - If N = 0, no Hive posts exist with that tag yet — publish your own via
/server-announce.html - If you see
[discovery] No response from Hive tag query, the Hive API is unreachable from inside the container. Test container DNS / outbound HTTPS by hitting any external HTTPS URL from the container.
Quick Reference
| Command | What it does |
|---|---|
docker compose ps |
Show status of all containers |
docker compose logs app |
Show app logs |
docker compose logs nginx |
Show Nginx logs |
docker compose logs -f app |
Watch live logs (Ctrl+C to stop) |
docker compose down |
Stop everything (always do this before rebuilding) |
docker compose build --no-cache |
Rebuild without using cached layers |
docker compose up -d |
Start everything in background |
docker compose down && docker compose build --no-cache && docker compose up -d |
Full rebuild cycle (use after any code change) |
docker compose restart nginx |
Restart Nginx after config-only changes (no rebuild needed) |
chown -R 1000:1000 /opt/v4call/data/logs |
Fix SQLite write permissions |
docker compose run --rm --entrypoint certbot certbot certificates |
List SSL certificates |
curl http://yourdomain.com/.well-known/acme-challenge/testfile |
Test certbot webroot works |
| escrow" | Check token payment logs |
docker compose logs -f app | grep -E "\[federation\]|\[discovery\]" |
Watch federation activity |
curl https://yourdomain.com/.well-known/v4call-server.json |
Check verify file is served |
https://yourdomain.com/server-sign.html |
Generate signed federation verify file |
https://yourdomain.com/server-announce.html |
Publish server to Hive directory |
https://yourdomain.com/admin-peers.html |
Manage federation peer approvals |
Optional: Password Protect Your Server During Testing
Optional: Password Protect Your Server During Testing (HTTP Basic Auth)
During development and testing you may want to restrict access so only people you invite can use your server. This uses Nginx HTTP Basic Auth — a simple username and password prompt that appears before the v4call login screen.
When a visitor cancels the login prompt they are shown a public info.html page where they can read about the project and request access.
Important: do NOT add basic auth to the /federation location block. Federation peers cannot supply HTTP credentials and the connection will fail. The auth lines below are added only to /socket.io/ and /.
- Step 1 — Install the htpasswd tool
apt install -y apache2-utils
- Step 2 — Create the password file and add your first user
htpasswd -c /opt/v4call/nginx/.htpasswd yourusername
You will be prompted to enter and confirm a password. The -c flag creates the file. Do not use -c again or it will overwrite the file and delete existing users.
To add more users later:
htpasswd /opt/v4call/nginx/.htpasswd anotherusername
To remove a user:
htpasswd -D /opt/v4call/nginx/.htpasswd username
- Step 3 — Create the public info page
nano /opt/v4call/public/info.html
Paste your HTML content — a page explaining the project and how to request access. This page is served publicly without a password so visitors who cancel the login prompt can still read it.
- Step 4 — Mount the files into the Nginx container
Edit docker-compose.yml and add two lines to the nginx volumes section:
nginx:
volumes:
- ./nginx/v4call.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro # add this
- ./public/info.html:/usr/share/nginx/html/info.html:ro # add this
- ./data/certbot/conf:/etc/letsencrypt:ro
- ./data/certbot/www:/var/www/certbot:ro
- Step 5 — Enable auth in your Nginx config
The HTTPS config from Step 13 already includes the auth_basic lines, just commented out. Open nginx/v4call.conf and uncomment them in BOTH the /socket.io/ and / location blocks (4 lines total, two pairs):
auth_basic "v4call — Private Testing";
auth_basic_user_file /etc/nginx/.htpasswd;
Do not uncomment auth in the /federation block — leave it as-is.
- Step 6 — Recreate the Nginx container to pick up the new volume mounts
Important: docker compose restart nginx is not enough — it reuses the old container and ignores new volume mounts. You must recreate it:
docker compose down && docker compose up -d
After this, verify the files are mounted inside the container:
docker compose exec nginx ls /usr/share/nginx/html/
You should see info.html listed alongside 50x.html and index.html.
- Step 7 — Test it works
curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/
Should return 401 (login required).
curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/info.html
Should return 200 (public, no auth needed).
curl -u youruser:yourpassword -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/
Should return 200 (correct credentials accepted).
- Managing users without restarting
After adding or removing users from the .htpasswd file, reload Nginx config without downtime:
docker compose exec nginx nginx -s reload
No restart needed — Nginx re-reads the .htpasswd file on every request anyway.
- Keep .htpasswd out of GitHub
The password file should never be committed to your repository. It's already in the project's .gitignore, but if you forked, double-check:
echo "nginx/.htpasswd" >> /opt/v4call/.gitignore
- Removing auth when you go public
When you are ready to open your server to everyone, simply re-comment the auth_basic lines from nginx/v4call.conf and reload:
docker compose exec nginx nginx -s reload
No other changes needed.
How to check if anyone tried to log in (failed attempts) or successfully logged in with .htpasswd on Nginx in Docker
Important Tip: Always run these commands in the same folder where your docker-compose.yml file is located. If you are in the wrong directory the commands will not find your container and nothing will show up.
- Basic Commands
- See everything live (good while testing)
docker compose logs -f nginx-fmeans "follow/live" – new log lines appear automatically. Remove-fif you only want to read the current logs once and stop.
- Best command to watch failed login attempts (people guessing passwords)
docker compose logs -f nginx | grep -E "mismatch|not found|401"
- See who successfully logged in
docker compose logs nginx | grep "remote_user"
- Combined view – most useful for daily checking (failed + successful)
docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"
- What the Logs Look Like
Failed attempt (wrong password):
2026/04/17 01:23:45 [error] ... user "admin": password mismatch, client: 185.123.45.67, ...
Failed attempt (wrong username):
2026/04/17 01:24:12 [error] ... user "hacker123" was not found in "/etc/nginx/.htpasswd", client: 45.67.89.10, ...
Successful login:
... "GET /protected/ HTTP/1.1" 200 ... remote_user: "myuser" ...
- How to Customise These Commands
- Change
nginxto the exact name of your service if it is different in docker-compose.yml. - Remove
-fto read the full log once without live following. - Change
--tail=100to--tail=500(or any number) to show more or fewer old lines. - Add or remove words in the
greppart to filter differently.
Examples: * Only failed attempts:grep -E "mismatch|not found"* Only 401 errors:grep " 401 "* Everything auth related:grep -E "(auth|password|mismatch|not found|remote_user)"
- Quick Copy-Paste Commands
- Watch failed guesses live
docker compose logs -f nginx | grep -E "mismatch|not found|401"
- Check successful logins
docker compose logs nginx | grep "remote_user"
- Combined quick check (recommended)
docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"