My server sleeps even though serverless is turned off

My API is written in GO and I have ensured that the serverless flag is turned off. However; after enough hours of inactivity the service will stop responding to all requests, I don't even see them show up in the HTTP log and I don't see any errors happening in any of the other logs. A simple server restart fixes the issue immediately. I am not sure if there is something configured incorrectly on my end or if it is an issue on railways end. Thanks in advance!

$20 Bounty

1 Replies

Railway
BOT

2 hours ago

This thread has been marked as public for community involvement, as it does not contain any sensitive or personal information. Any further activity in this thread will be visible to everyone.

Status changed to Open Railway about 2 hours ago


sheeki03
FREETop 10% Contributor

a few seconds ago

I would treat this as two separate checks: whether Railway is actually putting the service to sleep, and whether the Go process is still running but no longer reachable through the public domain.

The important detail is that you do not see the requests in your HTTP logs. If the log is written at the start of the handler and nothing appears, the request is probably not reaching your Go process. If the log is written after a database call or after the handler finishes, the app may still be receiving the request and blocking before your log line runs.

First, make sure the Go service is listening exactly the way Railway expects:

package main

import (
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	server := &http.Server{
		Addr:              "0.0.0.0:" + port,
		Handler:           logRequests(mux),
		ReadHeaderTimeout: 5 * time.Second,
	}

	log.Printf("listening on 0.0.0.0:%s", port)
	log.Fatal(server.ListenAndServe())
}

func logRequests(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf(
			"incoming request method=%s path=%s host=%s railway_request_id=%s",
			r.Method,
			r.URL.Path,
			r.Host,
			r.Header.Get("X-Railway-Request-Id"),
		)
		next.ServeHTTP(w, r)
	})
}

The two common mistakes are listening on localhost or 127.0.0.1, and hardcoding a port that is different from Railway's PORT variable. If your public domain has a target port set, make sure it matches the port your Go process is actually listening on.

Then add /healthz as the Railway healthcheck path. This will not continuously monitor the app after the deployment is live, but it does prove that Railway can reach the process before routing traffic to a new deployment. Keep this endpoint independent of MongoDB, Redis, third-party APIs, or any slow startup work. It should only prove that the Go server is alive.

For this specific symptom, I would test in this order:

  1. Confirm Serverless is disabled on the exact service and environment that owns the public domain.
  2. Confirm there is only one active deployment for that service and that the public domain points to this service, not an older service or duplicate environment.
  3. Confirm the app logs listening on 0.0.0.0:$PORT on boot.
  4. Confirm the public domain target port, if set, matches the app port.
  5. Add the request logger above and put the log line before any handler work.
  6. Add a lightweight /healthz endpoint and hit it after the service has been idle for a few hours.

If /healthz works after idle but your normal route hangs, the service is not asleep. The app is blocking inside the normal request path. In a Go API that usually means a stale database connection, an outbound HTTP call without a timeout, a locked goroutine, or startup state that only fails after sitting idle.

For any database or external HTTP work, make sure every request has a timeout:

client := &http.Client{
	Timeout: 10 * time.Second,
}

For database calls, use request-scoped contexts:

ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()

err := db.PingContext(ctx)

If /healthz also fails after idle and no request log appears, focus on Railway configuration: Serverless setting on the correct service, public networking target port, usage or replica limits, and whether the domain is attached to the expected service.


Welcome!

Sign in to your Railway account to join the conversation.

Loading...