Deploy Go web applications in production with systemd service management, NGINX reverse proxy with SSL termination, and comprehensive logging for monitoring and maintenance.
Prerequisites
- Root or sudo access
- Domain name for SSL certificates
- Basic Linux command line knowledge
What this solves
Go applications need proper service management, reverse proxy configuration, and SSL termination for production deployment. This tutorial sets up a complete production environment with systemd for process management, NGINX for reverse proxy and load balancing, and structured logging for monitoring.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all packages.
sudo apt update && sudo apt upgrade -y
Install Go compiler and development tools
Install the Go compiler and essential development tools for building web applications.
sudo apt install -y golang-go git curl nginx certbot python3-certbot-nginx
Verify Go installation
Check that Go is properly installed and configured with the correct version.
go version
go env GOPATH GOROOT
Create application user and directory structure
Create a dedicated user for running the Go application with proper permissions and directory structure.
sudo useradd --system --shell /bin/false --home /opt/goapp goapp
sudo mkdir -p /opt/goapp/{bin,logs,config}
sudo chown -R goapp:goapp /opt/goapp
Create sample Go web application
Create a production-ready Go web application with structured logging and graceful shutdown handling.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
type Server struct {
httpServer *http.Server
logger *log.Logger
}
type Response struct {
Message string json:"message"
Timestamp time.Time json:"timestamp"
Version string json:"version"
}
func NewServer() *Server {
logger := log.New(os.Stdout, "[GOAPP] ", log.LstdFlags|log.Lshortfile)
mux := http.NewServeMux()
server := &Server{
httpServer: &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
logger: logger,
}
mux.HandleFunc("/", server.handleHome)
mux.HandleFunc("/health", server.handleHealth)
mux.HandleFunc("/api/status", server.handleAPIStatus)
return server
}
func (s Server) handleHome(w http.ResponseWriter, r http.Request) {
s.logger.Printf("Home request from %s", r.RemoteAddr)
response := Response{
Message: "Go web application is running",
Timestamp: time.Now(),
Version: "1.0.0",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s Server) handleHealth(w http.ResponseWriter, r http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, {"status":"healthy","timestamp":"%s"}, time.Now().Format(time.RFC3339))
}
func (s Server) handleAPIStatus(w http.ResponseWriter, r http.Request) {
s.logger.Printf("API status request from %s", r.RemoteAddr)
response := map[string]interface{}{
"status": "operational",
"uptime": time.Since(startTime).String(),
"timestamp": time.Now(),
"version": "1.0.0",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s *Server) Start() error {
s.logger.Printf("Starting server on %s", s.httpServer.Addr)
return s.httpServer.ListenAndServe()
}
func (s *Server) Shutdown(ctx context.Context) error {
s.logger.Println("Shutting down server gracefully")
return s.httpServer.Shutdown(ctx)
}
var startTime = time.Now()
func main() {
server := NewServer()
go func() {
if err := server.Start(); err != nil && err != http.ErrServerClosed {
server.logger.Fatalf("Server failed to start: %v", err)
}
}()
server.logger.Println("Server started successfully")
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
server.logger.Fatalf("Server forced to shutdown: %v", err)
}
server.logger.Println("Server stopped gracefully")
}
Build the Go application
Compile the Go application with proper optimization flags for production deployment.
cd /opt/goapp
sudo -u goapp go mod init goapp
sudo -u goapp go mod tidy
sudo -u goapp go build -ldflags "-s -w" -o bin/goapp main.go
Create systemd service configuration
Configure systemd to manage the Go application with proper restart policies and resource limits.
[Unit]
Description=Go Web Application
After=network.target
Wants=network.target
[Service]
Type=exec
User=goapp
Group=goapp
WorkingDirectory=/opt/goapp
ExecStart=/opt/goapp/bin/goapp
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
TimeoutStopSec=30
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/goapp/logs
Resource limits
LimitNOFILE=65536
MemoryMax=512M
Logging
StandardOutput=append:/opt/goapp/logs/goapp.log
StandardError=append:/opt/goapp/logs/goapp-error.log
SyslogIdentifier=goapp
[Install]
WantedBy=multi-user.target
Create log rotation configuration
Configure logrotate to manage application logs and prevent disk space issues.
/opt/goapp/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0644 goapp goapp
postrotate
/bin/systemctl reload goapp.service > /dev/null 2>&1 || true
endscript
}
Enable and start the Go application service
Enable the systemd service to start automatically on boot and start it now.
sudo systemctl daemon-reload
sudo systemctl enable goapp.service
sudo systemctl start goapp.service
sudo systemctl status goapp.service
Configure NGINX reverse proxy
Set up NGINX as a reverse proxy with proper headers, caching, and security configurations.
upstream goapp_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Logging
access_log /var/log/nginx/goapp_access.log combined;
error_log /var/log/nginx/goapp_error.log warn;
# Health check endpoint
location /health {
proxy_pass http://goapp_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_bypass 1;
proxy_no_cache 1;
access_log off;
}
# API endpoints with caching
location /api/ {
proxy_pass http://goapp_backend;
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_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Rate limiting
limit_req zone=api burst=20 nodelay;
}
# Main application
location / {
proxy_pass http://goapp_backend;
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_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Basic caching for static responses
proxy_cache_valid 200 1m;
}
}
Rate limiting configuration
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
}
Configure NGINX main settings
Update the main NGINX configuration with performance optimizations and security settings.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
server_tokens off;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL Settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# Logging Settings
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# Gzip Settings
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Enable NGINX site configuration
Enable the Go application site and remove the default NGINX configuration.
sudo ln -sf /etc/nginx/sites-available/goapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx
Configure firewall rules
Set up firewall rules to allow HTTP, HTTPS, and SSH traffic while blocking direct access to the Go application port.
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status
Configure SSL certificates with Let's Encrypt
Set up automatic SSL certificates using Let's Encrypt and Certbot for production security.
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos --email admin@example.com --redirect
Set up automatic SSL renewal
Configure automatic SSL certificate renewal to maintain security without manual intervention.
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
sudo systemctl status certbot.timer
Verify your setup
Test that all components are working correctly with these verification commands:
# Check Go application service status
sudo systemctl status goapp.service
Check NGINX status
sudo systemctl status nginx
Test application directly
curl -i http://localhost:8080/health
Test through NGINX proxy
curl -i http://localhost/health
Test HTTPS (if SSL configured)
curl -i https://example.com/health
Check application logs
sudo tail -f /opt/goapp/logs/goapp.log
Check NGINX logs
sudo tail -f /var/log/nginx/goapp_access.log
You should see JSON responses from the health endpoints and successful HTTP status codes. For more advanced monitoring, consider setting up Prometheus and Grafana monitoring for comprehensive application metrics.
Production monitoring and logging
Configure structured logging
Set up structured JSON logging for better log parsing and monitoring integration.
package main
import (
"encoding/json"
"log"
"os"
"time"
)
type LogEntry struct {
Level string json:"level"
Message string json:"message"
Timestamp time.Time json:"timestamp"
Service string json:"service"
IP string json:"client_ip,omitempty"
Method string json:"method,omitempty"
Path string json:"path,omitempty"
Status int json:"status,omitempty"
Duration float64 json:"duration_ms,omitempty"
}
func LogInfo(message string, fields map[string]interface{}) {
entry := LogEntry{
Level: "info",
Message: message,
Timestamp: time.Now(),
Service: "goapp",
}
// Add custom fields
if ip, ok := fields["ip"].(string); ok {
entry.IP = ip
}
if method, ok := fields["method"].(string); ok {
entry.Method = method
}
if path, ok := fields["path"].(string); ok {
entry.Path = path
}
if status, ok := fields["status"].(int); ok {
entry.Status = status
}
if duration, ok := fields["duration"].(float64); ok {
entry.Duration = duration
}
if jsonData, err := json.Marshal(entry); err == nil {
log.Println(string(jsonData))
}
}
Configure application monitoring
Add basic metrics collection and monitoring endpoints for operational visibility.
package main
import (
"encoding/json"
"net/http"
"runtime"
"sync/atomic"
"time"
)
type Metrics struct {
RequestCount int64 json:"request_count"
ErrorCount int64 json:"error_count"
Uptime string json:"uptime"
GoRoutines int json:"goroutines"
MemoryUsage uint64 json:"memory_usage_bytes"
LastRequestTime time.Time json:"last_request_time"
}
var (
requestCount int64
errorCount int64
lastRequest time.Time
)
func incrementRequestCount() {
atomic.AddInt64(&requestCount, 1)
lastRequest = time.Now()
}
func incrementErrorCount() {
atomic.AddInt64(&errorCount, 1)
}
func (s Server) handleMetrics(w http.ResponseWriter, r http.Request) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
metrics := Metrics{
RequestCount: atomic.LoadInt64(&requestCount),
ErrorCount: atomic.LoadInt64(&errorCount),
Uptime: time.Since(startTime).String(),
GoRoutines: runtime.NumGoroutine(),
MemoryUsage: m.Alloc,
LastRequestTime: lastRequest,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metrics)
}
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service fails to start | Port already in use or permission denied | sudo lsof -i :8080 and sudo systemctl status goapp.service |
| 502 Bad Gateway from NGINX | Go application not running or connection refused | curl localhost:8080/health and check service status |
| Permission denied on log files | Incorrect file ownership or permissions | sudo chown -R goapp:goapp /opt/goapp/logs and sudo chmod 755 /opt/goapp/logs |
| SSL certificate errors | Domain not pointing to server or Certbot failure | Check DNS with dig example.com and retry sudo certbot --nginx |
| High memory usage | Memory leaks or insufficient garbage collection | Review application code and add GOGC=100 environment variable |
| NGINX configuration test fails | Syntax errors in configuration files | sudo nginx -t for detailed error messages |
Next steps
- Configure NGINX reverse proxy with SSL termination and load balancing for high availability
- Configure Linux system services with systemctl and service management
- Set up Prometheus and Grafana monitoring stack with Docker compose
- Configure Go application monitoring with Prometheus metrics
- Implement Go microservices with Docker and Kubernetes deployment
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
DOMAIN="${1:-}"
APP_USER="goapp"
APP_DIR="/opt/goapp"
SERVICE_NAME="goapp"
# Usage message
usage() {
echo "Usage: $0 <domain>"
echo "Example: $0 example.com"
exit 1
}
# Print colored messages
print_step() {
echo -e "${GREEN}[$1] $2${NC}"
}
print_error() {
echo -e "${RED}ERROR: $1${NC}" >&2
}
print_warning() {
echo -e "${YELLOW}WARNING: $1${NC}"
}
# Cleanup function for rollback
cleanup() {
if [[ $? -ne 0 ]]; then
print_error "Installation failed. Cleaning up..."
systemctl stop $SERVICE_NAME 2>/dev/null || true
systemctl disable $SERVICE_NAME 2>/dev/null || true
rm -f /etc/systemd/system/$SERVICE_NAME.service
userdel $APP_USER 2>/dev/null || true
rm -rf $APP_DIR
fi
}
trap cleanup ERR
# Check arguments
if [[ -z "$DOMAIN" ]]; then
usage
fi
# Check if running as root
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root"
exit 1
fi
# Detect distribution
print_step "1/10" "Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
NGINX_DEFAULT_SITE="default"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
NGINX_DEFAULT_SITE=""
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
NGINX_DEFAULT_SITE=""
FIREWALL_CMD="firewall-cmd"
;;
*)
print_error "Unsupported distribution: $ID"
exit 1
;;
esac
echo "Detected: $PRETTY_NAME"
else
print_error "Cannot detect distribution"
exit 1
fi
# Update system packages
print_step "2/10" "Updating system packages..."
$PKG_UPDATE
# Install required packages
print_step "3/10" "Installing required packages..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL golang-go git curl nginx certbot python3-certbot-nginx
else
$PKG_INSTALL golang git curl nginx certbot python3-certbot-nginx
fi
# Verify Go installation
print_step "4/10" "Verifying Go installation..."
go version
# Create application user and directories
print_step "5/10" "Creating application user and directories..."
useradd --system --shell /bin/false --home $APP_DIR $APP_USER 2>/dev/null || true
mkdir -p $APP_DIR/{bin,logs,config}
chown -R $APP_USER:$APP_USER $APP_DIR
chmod 755 $APP_DIR
chmod 755 $APP_DIR/{bin,logs,config}
# Create Go application
print_step "6/10" "Creating Go web application..."
cat > /tmp/main.go << 'EOF'
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
type Server struct {
httpServer *http.Server
logger *log.Logger
}
type Response struct {
Message string `json:"message"`
Timestamp time.Time `json:"timestamp"`
Version string `json:"version"`
}
var startTime = time.Now()
func NewServer() *Server {
logger := log.New(os.Stdout, "[GOAPP] ", log.LstdFlags|log.Lshortfile)
mux := http.NewServeMux()
server := &Server{
httpServer: &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
logger: logger,
}
mux.HandleFunc("/", server.handleHome)
mux.HandleFunc("/health", server.handleHealth)
mux.HandleFunc("/api/status", server.handleAPIStatus)
return server
}
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
s.logger.Printf("Home request from %s", r.RemoteAddr)
response := Response{
Message: "Go web application is running",
Timestamp: time.Now(),
Version: "1.0.0",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"status":"healthy","timestamp":"%s"}`, time.Now().Format(time.RFC3339))
}
func (s *Server) handleAPIStatus(w http.ResponseWriter, r *http.Request) {
s.logger.Printf("API status request from %s", r.RemoteAddr)
response := map[string]interface{}{
"status": "operational",
"uptime": time.Since(startTime).String(),
"timestamp": time.Now(),
"version": "1.0.0",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s *Server) Start() error {
s.logger.Printf("Starting server on %s", s.httpServer.Addr)
return s.httpServer.ListenAndServe()
}
func (s *Server) Shutdown(ctx context.Context) error {
s.logger.Println("Shutting down server gracefully")
return s.httpServer.Shutdown(ctx)
}
func main() {
server := NewServer()
go func() {
if err := server.Start(); err != nil && err != http.ErrServerClosed {
server.logger.Fatal("Server failed to start:", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
server.logger.Fatal("Server forced to shutdown:", err)
}
server.logger.Println("Server exited")
}
EOF
cd /tmp
go mod init goapp
go build -o $APP_DIR/bin/goapp main.go
chown $APP_USER:$APP_USER $APP_DIR/bin/goapp
chmod 755 $APP_DIR/bin/goapp
rm -f /tmp/main.go /tmp/go.mod
# Create systemd service
print_step "7/10" "Creating systemd service..."
cat > /etc/systemd/system/$SERVICE_NAME.service << EOF
[Unit]
Description=Go Web Application
After=network.target
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR
ExecStart=$APP_DIR/bin/goapp
Restart=always
RestartSec=5
StandardOutput=append:$APP_DIR/logs/app.log
StandardError=append:$APP_DIR/logs/error.log
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable $SERVICE_NAME
systemctl start $SERVICE_NAME
# Configure NGINX
print_step "8/10" "Configuring NGINX reverse proxy..."
if [[ "$PKG_MGR" == "apt" ]]; then
# Debian/Ubuntu configuration
cat > $NGINX_CONF_DIR/$DOMAIN << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
location /health {
proxy_pass http://127.0.0.1:8080/health;
access_log off;
}
}
EOF
ln -sf $NGINX_CONF_DIR/$DOMAIN $NGINX_ENABLED_DIR/
rm -f $NGINX_ENABLED_DIR/default
else
# RHEL/CentOS/Fedora configuration
cat > $NGINX_CONF_DIR/$DOMAIN.conf << EOF
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
location /health {
proxy_pass http://127.0.0.1:8080/health;
access_log off;
}
}
EOF
fi
nginx -t
systemctl enable nginx
systemctl restart nginx
# Configure firewall
print_step "9/10" "Configuring firewall..."
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw --force enable
ufw allow 'Nginx Full'
ufw allow ssh
else
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Verify installation
print_step "10/10" "Verifying installation..."
sleep 3
if systemctl is-active --quiet $SERVICE_NAME; then
echo -e "${GREEN}✓ Go application service is running${NC}"
else
print_error "Go application service is not running"
exit 1
fi
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ NGINX is running${NC}"
else
print_error "NGINX is not running"
exit 1
fi
if curl -s http://localhost:8080/health | grep -q "healthy"; then
echo -e "${GREEN}✓ Application health check passed${NC}"
else
print_error "Application health check failed"
exit 1
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo ""
echo "Next steps:"
echo "1. Set up SSL certificate: certbot --nginx -d $DOMAIN"
echo "2. Test your application at: http://$DOMAIN"
echo "3. Monitor logs: journalctl -u $SERVICE_NAME -f"
echo "4. View application logs: tail -f $APP_DIR/logs/app.log"
Review the script before running. Execute with: bash install.sh