Best self-hosted Calendly alternatives for VPS
Compare Cal.com, Easy!Appointments, Rallly and DayOtter on VPS, with focus on two-way calendar sync and whether outbound email fit reach Gmail.
The short answer
A self-hosted Calendly alternative get one job wey internal tools for your VPS never dey do: e must answer the public. The booking page na the product itself. E need real domain name and TLS (transport layer security) from day one, and e must deliver mail to people wey never hear about your server before.
Four projects cover the realistic options. Cal.com na the closest match to Calendly and the default choice for solo consultant. Easy!Appointments na the lightweight one, PHP and MySQL, and e dey work well for 1 GB VPS. Rallly na group poll tool and e no get booking page at all. DayOtter na the newest entrant, an AGPLv3 scheduling platform wey get confirm-first assistant in front of am.
Two questions go decide which one you fit actually run. E dey sync both ways with the calendar wey you already dey use? And e fit send mail? Na the second question cause most self-hosted booking setups to quietly fail, so make we start with am.
Outbound email na the part wey dey fail
Booking confirmation fit enter stranger inbox. Na transactional mail wey land for Gmail or Microsoft 365, and those receivers judge you based on sending IP address and your DNS records.
Sending directly from VPS almost never works. Most providers block outbound TCP port 25 for new accounts, so connection go hang, then timeout. Even when port 25 dey open, fresh VPS address no get sending history, and large receivers dey treat unknown hosting-range addresses as suspicious. Booking enter database, page show say e confirm, but nobody receive email. Nothing look broken from server side, na why client fit discover am weeks later when dem never show up.
Use relay. Any transactional mail provider fit work, and the app only need hostname, port, user, and password. Check whether port dey reachable before you change application config:
nc -vz -w 5 "$SMTP_HOST" 587A succeeded line mean say the path dey open. Hang or Connection refused mean say network block the port, and no amount of editing .env go fix am. Relays dey listen on 587 or 465 because port 25 dey blocked often.
Each project dey handle relay in its own way. Cal.com dey read EMAIL_FROM, EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, EMAIL_SERVER_USER and EMAIL_SERVER_PASSWORD, and e also accept RESEND_API_KEY instead. Pay attention to that one: the shipped .env.example points EMAIL_SERVER_HOST to localhost on port 1025, wey be local development mailbox. If you leave the default, the app go send into nowhere without error. Rallly dey use SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PWD. DayOtter dey use SMTP settings or Resend key. Easy!Appointments dey send notifications from the application, so point am to the same relay from its settings page before you accept real booking.
Then publish the DNS records wey your relay provide. SPF (sender policy framework) record talk which servers fit send for your domain, while DKIM (domainkeys identified mail) key sign each message so receiver fit prove say nobody alter am. Add DMARC (domain-based message authentication, reporting and conformance) policy after both checks pass. Send test booking to real address for large provider, open the message headers, and confirm say the authentication lines read pass. Booking page wey no fit send email worse pass no booking page, because e fail silently.
Which calendar backends really dey sync both ways
Sync get two directions, and dem fit fail separately. The read direction na availability: the app must see the busy blocks wey already dey your calendar, or e go give person a time slot wey you already occupy. The write direction na the booking itself: the confirmed event must show for the calendar wey you actually dey check, no be only inside the booking tool.
Google Calendar and Microsoft 365 support both directions, but one condition dey for self-hosted install. You must create the OAuth (open authorization) client yourself, because the hosted product client ID no dey inside the source code. For Cal.com, na GOOGLE_API_CREDENTIALS inside .env, and e hold the JSON wey you download from Google Cloud console. DayOtter dey use Google and Microsoft OAuth credentials the same way.
Two things dey cause problem here, and e good make you know dem before you start. First, the redirect URI wey you register must match your public URL exactly, including the scheme and any trailing path. If e no match, Google go stop the connection with redirect_uri_mismatch for the consent screen. Second, Google project wey remain for Testing publishing status dey issue refresh tokens wey expire after seven days. Sync go work throughout the week, then stop, and app logs go show invalid_grant for the next refresh. Move the consent screen to In production, or accept say you go reconnect by hand every Monday.
CalDAV (calendaring extensions to WebDAV) na the open option, but support no broad. Cal.com dey ship CalDAV app wey still get beta mark, and dem don verify am against servers including Baikal, Radicale, Nextcloud and Kerio Connect. Apple iCloud dey work through the same app, but e need app-specific password instead of your Apple ID password. DayOtter list Apple through CalDAV beside Google and Microsoft 365.
An ICS feed no be sync. A subscribed .ics URL na read-only by design, so e fit block time for your booking page, but e no fit ever receive the booking. If tool offer only ICS for your calendar, you get only half of the setup, and you go still dey copy events by hand.
Easy!Appointments dey sync Google Calendar and nothing else. Rallly no dey read availability at all: e dey collect votes for a set of candidate dates. Na the correct tool for "when six of us fit meet" and na the wrong tool for "book 30 minutes with me".
Booking page dey public, so TLS come first
Most of wetin people self-host dey private. Wiki, board, dashboard, all fit stay behind VPN or SSO login and never show for open internet. Booking link no fit do that. Anybody wey you send am to must load am, and this change the setup for 3 clear ways.
You need domain name wey get A record point to the VPS, before you install anything. You need certificate from day one, because browsers dey mark plain HTTP form as not secure, and your client dey type name and email inside am. You also need set the app public URL correctly for config, because that value dey inside the links for outgoing email and OAuth redirect URIs. Set NEXT_PUBLIC_WEBAPP_URL for Cal.com, DOMAIN for Rallly, BASE_URL for Easy!Appointments, or DAYOTTER_DOMAIN during installation, and set am to the https:// address wey you go really use.
Rallly and DayOtter dey handle TLS for you. Rallly bundled stack include Traefik and e dey issue Let's Encrypt certificates with the address for ACME_EMAIL. DayOtter installer dey bring up Caddy with automatic HTTPS. Cal.com and Easy!Appointments no dey do this, so you go put nginx in front and issue the certificate by yourself, the same way wey you go use for Let's Encrypt certificate for nginx with Certbot. Bind the app container to 127.0.0.1 so na only the proxy wey you control fit enter. If the same box already dey run self-hosted Trello alternative for your internal boards, keep am behind your existing auth and give only the booking host public server block.
Cal.com for your VPS
Docker configuration dey for im own repository, and images don already build for Docker Hub, so you go pull am instead of building am.
git clone --recursive https://github.com/calcom/cal.diy.git
cd cal.diy
cp .env.example .env
openssl rand -base64 32
openssl rand -base64 24
docker compose pull
docker compose up -dPut the first random value for NEXTAUTH_SECRET and the second one for CALENDSO_ENCRYPTION_KEY. You need both. Set DATABASE_URL and point NEXT_PUBLIC_WEBAPP_URL to your public address. The bundled stack na the web app, PostgreSQL and Prisma Studio. The docs provide docker compose up -d calcom for running only the app against database wey you host elsewhere. Na this one you go want after the installation don settle.
Pull the image; no build am for the VPS. The project instructions say make you export NODE_OPTIONS="--max-old-space-size=16384" when you dey build from source. This value na 16 GB heap for Node alone. For ARM hardware, add the -arm suffix to the image tag. The project no publish minimum requirement for running the prebuilt image. So, treat 2 GB for the app plus PostgreSQL as my working estimate, not as documented requirement. Monitor memory during the first week.
Check whether e don start:
docker compose ps
docker compose logs -f calcom
curl -sI https://cal.example.com | head -n 1The curl command suppose print HTTP/2 200. If nginx returns 502 Bad Gateway while the container dey show as running, e usually mean say the first boot still dey apply database migrations. Give am some minutes, then read the logs before you conclude say e don break. Cal.com webhooks dey fire for every confirmed booking, so one booking fit trigger any automation wey you already dey run. For example, one n8n instance wey dey reachable over HTTPS for your VPS.
The core na AGPLv3, but some features dey inside an enterprise directory under separate commercial licence. Read that licence before you use the team features to build paid business process.
Easy!Appointments for 1 GB box
Requirements na Apache or Nginx, PHP 8.2 or newer, and MySQL. Official image dey for alextselegidis/easyappointments.
Make we first talk one warning. The docker-compose.yml for the repository na development environment. E expect say you go open shell inside the container and run npm install && composer install && npm start. E no be deployment setup. Use the published image instead:
services:
easyappointments:
image: alextselegidis/easyappointments # pin the current tag from Docker Hub
environment:
- BASE_URL=https://book.example.com
- DB_HOST=mysql
- DB_NAME=easyappointments
- DB_USERNAME=easyapp
- DB_PASSWORD=change-me
ports:
- '127.0.0.1:8080:80'
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=change-me-too
- MYSQL_DATABASE=easyappointments
- MYSQL_USER=easyapp
- MYSQL_PASSWORD=change-me
volumes:
- ./mysql:/var/lib/mysqlBASE_URL must be the public HTTPS address. If you enter am wrong, booking links inside confirmation emails go point to host wey your client no fit reach. The image dey serve plain HTTP for port 80 and e no get certificate of its own. Na why dem bind the port to 127.0.0.1 and nginx terminate TLS for front. If compose syntax still new to you, start with Docker Compose basics for VPS and come back.
This na the lightest option here by far. Two containers, one PHP application and MySQL, fit run comfortably for 1 GB VPS. The trade-off na reach: Google Calendar na the only calendar backend, and the interface na traditional admin panel instead of modern booking flow. If your calendar na Microsoft 365, Fastmail or Nextcloud, remove this option before you start.
Rallly for group polls
Rallly dey answer different question. E no dey publish when you dey available. E dey put some possible times for front of group and collect votes. Na this you need for board meeting, but e no useful for client booking link.
curl -fsSL https://get.rallly.co | bashRead any script before you pipe am enter shell. Change bash to less, read wetin e dey do, then run am. The manual method dey do the same work step by step, so you fit see wetin each step dey do:
git clone https://github.com/lukevella/rallly-selfhosted.git
cd rallly-selfhosted
./rallly.sh setup
./rallly.sh startThe documented requirements na at least 2 GB of RAM, Docker 19.03 or newer with Compose v2, ports 80 and 443 wey dey free, and domain wey point to the server. The bundled stack na Traefik for HTTPS, the web application, PostgreSQL, and Garage for S3-compatible object storage. Set DOMAIN, a SECRET_PASSWORD wey get at least 32 characters, SUPPORT_EMAIL, and INITIAL_ADMIN_EMAIL. If you already dey run reverse proxy, set PROXY_MODE=external and WEB_PORT, and Traefik no go interfere. If you already dey run self-hosted S3-compatible object storage with MinIO, point the S3_* variables to am and remove the Garage container.
SMTP no be optional for here, because sign-in na magic link. If relay no dey work, nobody fit log in, including the admin account wey you just create. This na the better kind of email failure: e go stop you for the door instead of making you lose client booking three weeks later.
DayOtter, the newest entrant
DayOtter na AGPLv3 scheduling platform wey get assistant attached. The production install na one command:
curl -fsSL https://raw.githubusercontent.com/Dayotter/dayotter/main/deploy/install.sh \
| sudo DAYOTTER_DOMAIN=cal.example.com bashRead am before you run am, just as above. The installer go set up Docker, generate secrets, and bring up the full stack: the Next.js web app, background worker wey dey handle reminders, calendar sync, and webhooks, PostgreSQL, Redis, and Caddy with automatic HTTPS.
Calendar support broad pass the other three. E support Google, Microsoft 365, Apple through CalDAV, and ICS feeds, with the ICS caveat wey dey above. Every other integration na opt-in through environment variables, including SMTP or Resend for mail, ANTHROPIC_API_KEY for the assistant, Twilio for SMS, and Stripe for payments. The assistant dey confirm-first: e go propose, you approve, and nothing go reach your calendar without explicit yes. If you leave the API key empty, that part of the product simply no go run.
Licensing clean for self-hosters. The core na AGPLv3, and an ee/ directory carry commercial cloud-only licence wey remain inactive unless DAYOTTER_CLOUD=1 dey set. That mean the team features wey hosted plan dey bill at $9 per seat every month, as of August 2026, dey available for your own server.
This one also get the heaviest stack here and na the youngest project. Run am beside your existing booking link for two weeks, take real bookings through both, and read the worker logs before you move your clients over.
Wetin each stack really dey cost you
The number of containers na the honest way to know how much resources one stack go need for small VPS, because each service get its own minimum memory need. These na the numbers from each project own published Docker stack, checked for August 2026.
The data behind this chart
[
{
"tool": "Easy!Appointments",
"containers": 2,
"database": "MySQL"
},
{
"tool": "Cal.com",
"containers": 3,
"database": "PostgreSQL"
},
{
"tool": "Rallly",
"containers": 4,
"database": "PostgreSQL"
},
{
"tool": "DayOtter",
"containers": 5,
"database": "PostgreSQL and Redis"
}
]Easy!Appointments need 2 containers and e fit run for 1 GB. Rallly bundled stack get 4, and its documentation ask for 2 GB. DayOtter installer start 5, na why e need the biggest server among the 4 wey dey here. Cal.com and DayOtter no publish any minimum memory figure, so 2 GB na where I dey start for both, not a number dem officially support.
Two of these container counts go reduce if you already dey run the infrastructure. Rallly Traefik and Garage containers go comot when you point am to your own proxy and object storage. Cal.com Prisma Studio na development tool; you no suppose leave am running for public server.
Wich self-hosted Calendly alternative you suppose pick
Solo consultant suppose use Cal.com. Na only project for here wey combine booking page wey people sabi, prebuilt images wey save your VPS from Node build, and CalDAV path for people wey no dey keep calendar for Google or Microsoft. One PostgreSQL database and one application container na maintenance load wey you fit manage for years. Keep one afternoon for the OAuth client and mail relay, and remember say the CalDAV app still dey beta. So test one real booking from beginning to end before you publish the link.
Small team suppose check DayOtter. Weighted round robin and collective booking dey inside the AGPLv3 core. So when you self-host am, you get features wey hosted seat dey charge for. The worker process dey built for reminders and webhooks wey real team dey depend on. The trade-off na maturity: na the newest project for this list. Run am alongside the old system first, and keep the old link active until you don monitor one full month of bookings.
Two narrower cases. If na only poll you need to find time wey the group fit meet, install Rallly and stop there. If you get 1 GB VPS, you dey use Google Calendar, and you want the smallest thing wey fit take booking, Easy!Appointments go last pass every fancier option wey you fit put for that box. For the wider question of wetin deserve space for the same server, see wetin dey worth self-hosting for 2026.
FAQ
I fit run self-hosted booking page without domain name?
No. All these apps dey write their public URL inside links for confirmation emails, and Google plus Microsoft dey compare OAuth redirect URI with that same value. So bare IP address go give you redirect_uri_mismatch for consent screen. Let's Encrypt no go issue certificate for IP address too, so page go load over plain HTTP and browser go mark the form as not secure. Buy the domain first, point an A record to the VPS, then install.
Why my booking confirmation emails no dey ever arrive?
Almost every time, na because server dey try deliver the mail by itself. Most VPS providers dey block outbound port 25 for new accounts, so connection dey hang. Even when dem open am, new address no get sending reputation, and large receivers go reject am. Point the app to transactional mail relay for port 587. Confirm say the port dey reachable with nc -vz -w 5 "$SMTP_HOST" 587, then publish the SPF and DKIM records wey the relay give you. If you dey run Cal.com, check say you replace the shipped EMAIL_SERVER_HOST=localhost and EMAIL_SERVER_PORT=1025 defaults. Dem dey point to local development mailbox.
Self-hosted Cal.com dey sync with CalDAV, or na only Google?
Both, but dem no get the same maturity. The CalDAV app dey marked beta, and dem don verify am with servers including Baikal, Radicale, Nextcloud, and Kerio Connect. Apple iCloud dey work through am with app-specific password. Google Calendar and Microsoft 365 both dey sync in both directions. But for self-hosted install, you must create your own OAuth client and provide am through GOOGLE_API_CREDENTIALS, because the hosted service credentials no dey inside the source.
Why my Google Calendar sync dey stop working after one week?
Because the Google Cloud project still dey for Testing publishing status. Google dey issue refresh tokens to apps for that state, but dem dey expire after seven days. So connection go work, then e go stop during the next token refresh, and application log go show invalid_grant. Move the OAuth consent screen to In production, then reconnect the calendar once. If you reconnect without changing the status, you go get another seven days only.
Which one among these go run for 1 GB VPS?
Easy! Easy!Appointments go run, because na PHP application plus MySQL. Rallly documentation specify 2 GB minimum, and the bundled stack dey run four services. Cal.com and DayOtter no publish minimum requirement, but Next.js application with PostgreSQL, plus Redis and worker process for DayOtter, mean say you suppose plan for 2 GB or more. Never build Cal.com from source for small server. The project build instructions ask for 16 GB Node heap, so use the prebuilt image instead.