Echo Production Configuration
When you're ready to deploy your Echo application to production, there are several important configuration considerations to ensure your application is secure, performant, and reliable. This guide will walk you through the essential production configuration steps for your Echo web applications.
Introduction
Moving an Echo application from development to production requires careful configuration to handle real-world traffic, security threats, and performance demands. Production environments differ significantly from development environments, and proper configuration is crucial for maintaining application stability and security.
In this guide, we'll cover:
- Server configuration
- Security settings
- Logging
- Performance optimization
- Error handling
- Health checks and monitoring
Basic Production Server Configuration
Let's start with a basic production-ready Echo server configuration:
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
"time"
)
func main() {
// Create a new Echo instance
e := echo.New()
// Production configurations
e.Debug = false // Disable debug mode in production
// Start server with graceful shutdown
s := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
IdleTimeout: 120 * time.Second,
}
e.Logger.Fatal(e.StartServer(s))
}
In this basic setup, we've disabled debug mode and configured timeouts which are crucial for preventing resource exhaustion in production environments.
Essential Production Middleware
Echo provides several middleware components that are especially important in production:
// Add production middleware
e.Use(middleware.Recover())
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSMaxAge: 31536000,
HSTSExcludeSubdomains: false,
ContentSecurityPolicy: "default-src 'self'",
}))
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: 5, // Default compression level
}))
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20))) // 20 requests per second
Each of these middleware components serves an important purpose:
- Recover: Catches panics and converts them to errors
- Secure: Sets various security headers to protect against common web vulnerabilities
- Gzip: Compresses responses to improve performance
- RateLimiter: Prevents abuse by limiting request rates
Configuring Proper Logging
In production, structured logging is essential for monitoring and debugging:
// Configure structured JSON logger
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
Output: os.Stdout,
}))
You might also want to redirect logs to a file or external logging service:
// Log to file
logFile, err := os.OpenFile("echo.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
e.Logger.Fatal(err)
}
e.Logger.SetOutput(logFile)
CORS Configuration for Production
If your API serves clients from different domains, you'll need to configure CORS carefully:
// Configure CORS for production
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourdomain.com", "https://www.yourdomain.com"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
MaxAge: 86400, // 24 hours
}))
This configuration restricts access to specific domains, methods, and headers, which is much more secure than the permissive settings often used during development.
TLS Configuration for HTTPS
HTTPS is non-negotiable for production applications. Here's how to configure TLS:
// TLS configuration
e.Logger.Fatal(e.StartTLS(":443", "cert.pem", "key.pem"))
For more advanced TLS configurations:
// Custom TLS configuration
s := &http.Server{
Addr: ":443",
Handler: e,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
}
e.Logger.Fatal(s.ListenAndServeTLS("cert.pem", "key.pem"))
Database Connection Management
Properly managing database connections is crucial in production:
// Configure database with connection pooling
db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname")
if err != nil {
e.Logger.Fatal(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
These settings help prevent connection exhaustion and ensure efficient resource usage.
Health Check Endpoint
A health check endpoint is essential for container orchestration systems and load balancers:
// Health check endpoint
e.GET("/health", func(c echo.Context) error {
// Check dependencies like database, cache, etc.
if dbErr := db.Ping(); dbErr != nil {
return c.JSON(http.StatusServiceUnavailable, map[string]string{
"status": "error",
"message": "Database connection failed",
})
}
return c.JSON(http.StatusOK, map[string]string{
"status": "ok",
"version": "1.0.0",
})
})
Graceful Shutdown
Properly shutting down your application ensures that in-flight requests are completed:
// Graceful shutdown
go func() {
if err := e.StartServer(s); err != nil && err != http.ErrServerClosed {
e.Logger.Fatal("shutting down the server")
}
}()
// Wait for interrupt signal to gracefully shut down the server
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
Complete Production Configuration Example
Here's a complete example that incorporates all the production best practices we've discussed:
package main
import (
"context"
"database/sql"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
"os"
"os/signal"
"time"
_ "github.com/lib/pq" // PostgreSQL driver
)
func main() {
// Create a new Echo instance
e := echo.New()
// Production configurations
e.Debug = false
// Connect to database
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
e.Logger.Fatal(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
defer db.Close()
// Add production middleware
e.Use(middleware.Recover())
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSMaxAge: 31536000,
HSTSExcludeSubdomains: false,
ContentSecurityPolicy: "default-src 'self'",
}))
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: 5,
}))
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
// Configure structured logging
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
Output: os.Stdout,
}))
// Configure CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourdomain.com", "https://www.yourdomain.com"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
MaxAge: 86400,
}))
// Health check endpoint
e.GET("/health", func(c echo.Context) error {
if dbErr := db.Ping(); dbErr != nil {
return c.JSON(http.StatusServiceUnavailable, map[string]string{
"status": "error",
"message": "Database connection failed",
})
}
return c.JSON(http.StatusOK, map[string]string{
"status": "ok",
"version": "1.0.0",
})
})
// Your routes go here
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, Production!")
})
// Start server with graceful shutdown
s := &http.Server{
Addr: ":" + os.Getenv("PORT"),
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
IdleTimeout: 120 * time.Second,
}
go func() {
if err := e.StartServer(s); err != nil && err != http.ErrServerClosed {
e.Logger.Fatal("shutting down the server")
}
}()
// Wait for interrupt signal to gracefully shut down the server
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
}
Environment-based Configuration
In practice, you'll want to load configuration from environment variables or configuration files:
// Load configuration from environment variables
port := os.Getenv("PORT")
if port == "" {
port = "8080" // Default port
}
// Use environment-specific settings
if os.Getenv("ENVIRONMENT") == "production" {
// Production-specific settings
e.Debug = false
// More production settings...
} else {
// Development settings
e.Debug = true
// More development settings...
}
Summary
Properly configuring your Echo application for production is crucial for security, performance, and reliability. Key considerations include:
- Security: Disable debug mode, use secure headers, configure CORS, and enable HTTPS
- Performance: Set appropriate timeouts, use compression, and configure connection pools
- Reliability: Implement graceful shutdown, health checks, and proper error handling
- Monitoring: Configure structured logging for better observability
By following these best practices, you'll be well on your way to a robust, production-ready Echo application that can handle real-world traffic and challenges.
Additional Resources and Exercises
Resources
- Official Echo Framework Documentation
- Go Production Best Practices
- Web Application Security Checklist
Exercises
-
Security Audit: Review your Echo application's security settings against the OWASP Top 10 vulnerabilities.
-
Load Testing: Use a tool like
hey
orwrk
to load test your Echo application and adjust configurations for optimal performance:bash# Install hey
go get -u github.com/rakyll/hey
# Run a load test
hey -n 10000 -c 100 http://localhost:8080/ -
Configuration Management: Implement a configuration system that loads settings from environment variables, flags, and configuration files with appropriate precedence.
-
Monitoring Integration: Extend the health check endpoint to include more comprehensive application health metrics, and integrate with a monitoring solution like Prometheus.
-
Dockerization: Create a production-ready Dockerfile for your Echo application that follows security best practices, including running as a non-root user.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)