To install BillionMail on Hostinger VPS, you need a VPS running Ubuntu with Docker, the official BillionMail installer, and a few open ports for the admin panel and mail services. The install itself takes minutes. Most of the time goes on what comes after: finding the right login URL, opening the right ports, sorting out conflicts with tools like Coolify, and fixing database migration errors if your version has them.
We recently set up BillionMail on a Hostinger VPS for a self-hosted email marketing server and ran into several problems that the standard instructions do not cover. This guide walks through the full setup and every fix we used, in the order you are likely to need them.
- BillionMail is a free, self-hosted mail server and email marketing platform that runs in Docker containers.
- Get your login details with
bash bm.sh default. Use the full URL it prints, including the SafePath, or you will see “access denied”. - If you already run Coolify or Traefik, keep BillionMail off ports 80 and 443 and check port usage before you install.
- The PostgreSQL error column “1” does not exist is a query builder issue in some versions, fixed with
gdb.Raw("1"). - Never delete
bm_relay_oldor change source code without a database and file backup first. - A working dashboard does not mean email delivery works. Set up SPF, DKIM, DMARC, MX and reverse DNS, then test.
What is BillionMail?
BillionMail is an open-source mail server and email marketing platform that you host on your own server. It bundles everything needed to send and receive email and run campaigns: a web dashboard, contact lists, campaign sending, SMTP settings, domain management and delivery statistics, plus webmail for mailboxes.
Behind the dashboard, BillionMail runs as a set of Docker containers that work together:
| Service | What it does |
|---|---|
| Core application | The dashboard, campaigns, contacts and settings |
| PostgreSQL | Stores accounts, domains, contacts and campaign data |
| Redis | Caching and queues |
| Postfix | Sends and receives email (SMTP) |
| Dovecot | Mailbox access over IMAP and POP3 |
| Rspamd | Spam filtering and DKIM signing |
| Webmail | Browser-based mailbox access |
The appeal is control and cost: no per-contact pricing and your data stays on your server. The trade-off is that you are responsible for the server, security and deliverability yourself.
Before you install BillionMail on Hostinger VPS
This is the environment we used. Yours may differ, so check your server before you start.
| Component | Our setup |
|---|---|
| Hosting | Hostinger VPS |
| Operating system | Ubuntu 24.04 LTS |
| Containers | Docker with Docker Compose |
| Database | PostgreSQL (installed by BillionMail) |
| Existing proxy | Coolify with Traefik on ports 80 and 443 |
| BillionMail admin ports | 8888 (HTTP) and 8443 (HTTPS) |
Before installing, check:
- Resources: free RAM, CPU and disk space, especially if other apps already run on the VPS. Check the current minimum requirements in the official BillionMail repository.
- A domain you can manage: you will need to add DNS records for sending.
- Existing services and ports: anything already using ports 80, 443, 25 or the mail ports will conflict.
- Outbound SMTP: some VPS providers restrict outbound traffic on port 25. Check Hostinger’s current policy for your plan before relying on the server for delivery.
- A fresh snapshot: take a VPS snapshot or backup from the Hostinger panel so you can roll back.
Step 1: Check that Docker is installed
BillionMail runs everything in Docker. Connect to your VPS over SSH and check Docker and Docker Compose:
docker --version
docker compose version
Then see what is already running, so you know which containers and ports are in use:
docker ps
If Docker is missing, install it using Docker’s official instructions for Ubuntu, or choose a Hostinger VPS template that includes Docker.
Step 2: Install BillionMail
Use the installation method in the official BillionMail repository, because the maintainers update it. At the time of writing, the one-line installer clones the project into /opt/BillionMail and runs the install script:
cd /opt && git clone https://github.com/aaPanel/BillionMail && cd BillionMail && bash install.sh
When it finishes, check the project files and running containers:
cd /opt/BillionMail
ls -la
docker ps
You should see containers for the core application, PostgreSQL, Redis, Postfix, Dovecot, Rspamd and webmail. Read the installer output carefully: it lists the ports you need to open and may warn about conflicts.
Step 3: Find your login URL and password
Do not guess the login address or credentials. BillionMail has a command that prints them:
cd /opt/BillionMail
bash bm.sh default
The output looks similar to this:
BillionMail default info!
BillionMail Internet IPv4 Address:
https://YOUR_SERVER_IP:8443/billion
Username: billion
Password: billion
Open the full URL it prints, including the last part (the SafePath, /billion in this example). On our server the username and password were both billion, but yours may differ, so always use what the command shows. Change the admin password as soon as you log in.
Your browser will warn about the certificate on first visit because the panel uses a self-signed certificate on the IP address. That is expected until you put the panel behind a domain with a proper SSL certificate.
Step 4: Open the right ports on your Hostinger VPS
If the dashboard or mail services cannot be reached from outside, the firewall is the usual cause. Check both the Ubuntu firewall (if enabled) and the firewall rules in your Hostinger VPS panel. Depending on which services you use, the ports are:
| Port | Service |
|---|---|
| 25 | SMTP (server-to-server mail) |
| 465 | SMTP over SSL |
| 587 | SMTP submission |
| 110 / 995 | POP3 / POP3S |
| 143 / 993 | IMAP / IMAPS |
| 8888 | BillionMail admin (HTTP) |
| 8443 | BillionMail admin (HTTPS) |
Open only what you actually use. Where possible, limit the admin ports to your own IP address rather than the whole internet. The same “smallest attack surface” rule we use in our WordPress security checklist applies to mail servers too.
Step 5: Set up your domain and DNS before sending
Once you can log in, add your sending domain in BillionMail. It will show the DNS records your setup needs. Typically these are:
- A record for the mail hostname, pointing to your VPS IP.
- MX record so other servers know where to deliver mail for your domain.
- SPF to say which servers may send for your domain.
- DKIM to sign your emails so receivers can verify them.
- DMARC to tell receivers what to do with mail that fails SPF or DKIM.
- PTR (reverse DNS) so your IP resolves back to your mail hostname. Set this in your Hostinger VPS panel or through support.
Use the exact values BillionMail and your DNS provider give you. Do not copy SPF, DKIM or MX values from another server or tutorial. Then send test emails to Gmail and Outlook accounts and check they land in the inbox before you send a real campaign.
We set up and fix self-hosted servers, Docker apps and websites for clients in Pakistan, the USA, UK and worldwide. If your install is failing, the dashboard will not open or emails are not delivering, send us the error and we will tell you what is wrong.
Get help with your server → or WhatsApp +92 315 3038021
BillionMail troubleshooting: problems we hit and how we fixed them
These are the real issues from our installation. Some only affect certain BillionMail versions, so check your own logs and code before applying any fix.
1. PostgreSQL error: column “1” does not exist
The core application logs showed:
Failed to cleanup duplicates or create unique index
ERROR: column "1" does not exist
The failing query was checking whether a database index already existed:
SELECT "1"
FROM "pg_indexes"
WHERE "indexname" = 'uk_relay_domain'
LIMIT 1;
Why it happens: the query builder wrapped 1 in double quotes. In PostgreSQL, double quotes mean “this is a column name”, so it looked for a column called 1, which does not exist.
The fix: in core/internal/service/database_initialization/smtp_relay.go, the code used:
Fields("1")
We changed it to GoFrame’s raw expression, so 1 is sent as a plain number:
exists, err := g.DB().Model("pg_indexes").Fields(gdb.Raw("1")).
Where("indexname", "uk_relay_domain").
Value()
This is a source-level workaround for the version we tested. Back up the original file, and check whether a newer BillionMail release already fixes it before you patch anything.
2. “Table bm_relay_old already exists”
The next startup message was:
Failed to rename old table bm_relay:
Table bm_relay_old already exists,
skipping rename operation
This means the migration tried to rename the old relay table, but a table with the target name was already there, usually left over from an earlier run. First, look at what exists:
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public';
On our server the relevant tables were:
bm_relay_config
bm_relay_domain_mapping
bm_relay_old
We reviewed the migration condition in the source, adjusted it so it no longer tried to repeat the rename, then rebuilt and redeployed. After that the core container stayed running and the relay migration error no longer appeared in the logs.
Do not delete bm_relay_old just because the name says “old”. It may hold relay settings needed for migration or recovery. Take a database dump first and understand the migration logic before you change anything.
3. The build script fails on Ubuntu (apk: command not found)
To apply the source fixes we had to rebuild the core binary. The project’s build script failed because it calls apk, which is the package manager for Alpine Linux, not Ubuntu.
Instead of changing the server, we built inside a temporary Alpine Go container:
docker run --rm \
-v /opt/BillionMail/core:/opt/core \
-w /opt/core \
golang:1.24-alpine \
sh -c 'go mod download && GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o billionmail-amd64 main.go && ls -lh billionmail-amd64'
What each part does:
golang:1.24-alpinegives an Alpine environment with Go installed.-vmounts the BillionMail source folder into the container.GOOS=linux GOARCH=amd64builds for a 64-bit Linux server.--rmdeletes the temporary container when the build finishes.
Use the Go version the project requires (check go.mod). Keep a copy of the original Docker image before replacing it with your custom build, so you can switch back.
4. Login page shows “access denied”
This almost always means the wrong URL or wrong credentials. Opening the server IP, or adding /login yourself, skips the SafePath that BillionMail uses to hide the admin panel. Run bash bm.sh default again and copy the exact URL and credentials it prints.
5. The dashboard will not open
Check the core container first:
docker inspect billionmail-core-billionmail-1 \
--format 'Status={{.State.Status}} RestartCount={{.RestartCount}}'
You want Status=running and a restart count that is not climbing. Then check that the panel responds locally:
curl -sS -o /dev/null -w 'HTTP Status: %{http_code}\n' http://127.0.0.1:8888/
A 200 means the app is working and the problem is the firewall, ports or the URL you are using. If the container keeps restarting, read the logs:
docker logs --tail 100 billionmail-core-billionmail-1
Look for database connection errors, migration failures like the two above, or configuration problems. Container names can differ between versions, so confirm yours with docker ps.
6. Port conflicts with Coolify and Traefik
If Coolify already runs on your VPS, its Traefik proxy normally holds ports 80 and 443. Two services cannot share the same host port. Check what is listening before you change anything:
ss -tulpn
docker ps
We kept BillionMail’s admin panel on 8888 and 8443, which avoided the conflict. If you want the panel on a domain with a proper SSL certificate, route it through Traefik as a reverse proxy rather than changing Coolify’s port mappings, which can take your other apps offline.
7. Fail2ban: “Could not find server”
The core logs also showed:
fail2ban: ERROR Could not find server
This came from the Fail2ban security service, not the database, and did not stop the dashboard working. A warning from one supporting service does not mean all of BillionMail is down. Confirm the core app separately:
docker inspect billionmail-core-billionmail-1 --format '{{.State.Status}}'
curl -I http://127.0.0.1:8888/
Do not ignore repeated Fail2ban errors forever, because Fail2ban is what blocks password-guessing attacks. Investigate it as its own issue, and do not make database changes to fix it.
How to check BillionMail is running correctly
After any fix, run the same health check:
# All containers up?
docker ps
# Core app running and not restarting?
docker inspect billionmail-core-billionmail-1 \
--format 'Status={{.State.Status}} RestartCount={{.RestartCount}}'
# Panel responding locally?
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8888/
# Any new errors?
docker logs --tail 100 billionmail-core-billionmail-1
On our server, after the fixes, the core container was running with a restart count of zero and the panel returned 200. Remember what this proves and what it does not: the application is working, but outbound delivery, DNS authentication and mailbox access still need their own tests.
Back up before you troubleshoot
Before changing source code, database tables or Docker images, make a recovery point:
- PostgreSQL: take a database dump with
pg_dumpfrom inside the database container. - Configuration: copy
.env,docker-compose.yml, mail and SSL configuration and the data folders. - Docker image: tag the original core image before you replace it with a custom build.
- The whole VPS: take a snapshot in the Hostinger panel.
Keeping the server and its software updated matters as much as backups. Our article on the PHP2Shell vulnerability shows how quickly unpatched server software becomes a target, and our guide on what to do if your site is hacked explains why tested, off-server backups are the difference between a bad day and a disaster.
Should you self-host your email marketing?
Self-hosting with BillionMail makes sense if you send at volume, want to own your data and are comfortable managing a Linux server. It is a poor fit if nobody on your team can maintain the server, because deliverability, security updates and blacklist monitoring become your job. A new IP address also needs to build a sending reputation, so start with small volumes to engaged contacts and grow gradually.
If your emails are mainly order confirmations and contact form notifications from a WordPress or WooCommerce site, a reliable transactional SMTP service is often simpler. We set these up as part of our WordPress website development and WooCommerce development work.
Frequently Asked Questions
Can I install BillionMail on a Hostinger VPS?
Yes. BillionMail runs in Docker, so you can install it on a Hostinger VPS running a supported Linux system such as Ubuntu 24.04. Check the current requirements in the official BillionMail repository and make sure no other service already uses the ports BillionMail needs.
What is the default BillionMail login URL?
Run bash bm.sh default inside /opt/BillionMail. It prints the full login URL for your server, including the SafePath, for example https://YOUR_SERVER_IP:8443/billion. Opening the bare IP address or /login usually will not take you to the right page.
What is the default BillionMail username and password?
The bash bm.sh default command shows the credentials for your installation. On the server in this guide they were billion and billion, but yours may differ. Change the password straight after your first login.
Why does BillionMail show “access denied”?
Usually because the login URL is missing the SafePath or the username and password are wrong. Run bash bm.sh default to see the exact URL and credentials for your server, then log in using that full URL.
Why does PostgreSQL show “column 1 does not exist” in BillionMail?
In the version we tested, the query builder wrapped 1 in quotes, so PostgreSQL treated it as a column name instead of a number. Changing Fields(“1”) to Fields(gdb.Raw(“1”)) in smtp_relay.go fixed it. Check whether your release still has this code before changing anything.
Can BillionMail run alongside Coolify on the same VPS?
Yes, as long as the two do not fight over the same host ports. Coolify’s Traefik proxy normally uses ports 80 and 443, so keep BillionMail’s admin panel on its own ports such as 8888 and 8443, or route it through Traefik with a proper reverse proxy setup.
Is BillionMail ready to send emails straight after installation?
No. The dashboard working only proves the application is running. You still need to add your sending domain, publish SPF, DKIM, DMARC and MX records, set reverse DNS (PTR), confirm your VPS allows outbound SMTP and send test emails before running any campaign.
Need help with BillionMail or your server?
Codixity builds websites and sets up the servers behind them for clients in Pakistan, the USA, UK and worldwide, with 350+ projects over 6+ years. We can install BillionMail on your Hostinger VPS, fix errors like the ones above, configure DNS for delivery and keep everything updated. See our custom web app development service, or contact us directly:
- Email: [email protected]
- WhatsApp / Pakistan: +92 315 3038021
- UK: +44 7491 348886
Contact Codixity for a fixed quote →
Note: this guide documents a real installation on a specific BillionMail version and VPS setup. Application versions and installer behaviour change, so check the official BillionMail documentation and back up your system before applying any source code change.


