gRPC Server Deployment and Configuration

gRPC is a high-performance RPC framework from Google that uses Protocol Buffers for serialization and HTTP/2 for transport, making it ideal for microservices communication with strong typing and bidirectional streaming. This guide covers deploying gRPC services in production on Linux, including protobuf compilation, TLS setup, load balancing with Envoy, health checking, and client-side configuration.

Prerequisites

  • Ubuntu 20.04+ or CentOS/Rocky 8+
  • Go 1.21+ or Python 3.9+ (this guide uses Go examples)
  • Root access
  • Basic understanding of Protocol Buffers

Installing gRPC and Protocol Buffers

# Install protoc (Protocol Buffer compiler)
PB_VERSION="25.3"
wget https://github.com/protocolbuffers/protobuf/releases/download/v${PB_VERSION}/protoc-${PB_VERSION}-linux-x86_64.zip
sudo unzip protoc-${PB_VERSION}-linux-x86_64.zip -d /usr/local
sudo chmod +x /usr/local/bin/protoc

# Verify
protoc --version

Install Go protoc plugins:

# Install Go
export PATH=$PATH:/usr/local/go/bin

# Install protoc-gen-go and protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Add Go bin to PATH
export PATH=$PATH:$(go env GOPATH)/bin
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrc

For Python:

pip install grpcio grpcio-tools
# Compile with: python -m grpc_tools.protoc ...

Defining Protobuf Services

Create your service definitions:

mkdir -p /opt/mygrpc/proto
cat > /opt/mygrpc/proto/user.proto << 'EOF'
syntax = "proto3";

package user;
option go_package = "github.com/yourorg/mygrpc/gen/user;userpb";

import "google/protobuf/timestamp.proto";

service UserService {
    // Unary RPC
    rpc GetUser (GetUserRequest) returns (User);
    rpc CreateUser (CreateUserRequest) returns (User);

    // Server-side streaming
    rpc ListUsers (ListUsersRequest) returns (stream User);

    // Client-side streaming
    rpc BulkCreateUsers (stream CreateUserRequest) returns (BulkCreateResponse);

    // Bidirectional streaming
    rpc SyncUsers (stream UserSyncRequest) returns (stream UserSyncResponse);
}

message User {
    string id = 1;
    string name = 2;
    string email = 3;
    google.protobuf.Timestamp created_at = 4;
}

message GetUserRequest {
    string id = 1;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
}

message ListUsersRequest {
    int32 page_size = 1;
    string page_token = 2;
}

message BulkCreateResponse {
    int32 created = 1;
    repeated string errors = 2;
}

message UserSyncRequest {
    string user_id = 1;
    string action = 2;  // "create", "update", "delete"
}

message UserSyncResponse {
    bool success = 1;
    string message = 2;
}
EOF

Compile the protobuf:

cd /opt/mygrpc

# Create output directory
mkdir -p gen/user

# Generate Go code
protoc \
    --go_out=./gen/user \
    --go_opt=paths=source_relative \
    --go-grpc_out=./gen/user \
    --go-grpc_opt=paths=source_relative \
    -I ./proto \
    proto/user.proto

# Verify generated files
ls gen/user/
# user.pb.go  user_grpc.pb.go

Implementing a gRPC Server (Go Example)

cat > /opt/mygrpc/cmd/server/main.go << 'EOF'
package main

import (
    "context"
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
    "google.golang.org/grpc/keepalive"
    "google.golang.org/grpc/reflection"

    userpb "github.com/yourorg/mygrpc/gen/user"
)

type userServer struct {
    userpb.UnimplementedUserServiceServer
}

func (s *userServer) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.User, error) {
    // Implement your logic here
    return &userpb.User{
        Id:    req.Id,
        Name:  "John Doe",
        Email: "[email protected]",
    }, nil
}

func (s *userServer) ListUsers(req *userpb.ListUsersRequest, stream userpb.UserService_ListUsersServer) error {
    // Stream users to client
    users := []userpb.User{
        {Id: "1", Name: "Alice", Email: "[email protected]"},
        {Id: "2", Name: "Bob", Email: "[email protected]"},
    }
    for _, u := range users {
        user := u
        if err := stream.Send(&user); err != nil {
            return err
        }
        time.Sleep(100 * time.Millisecond)
    }
    return nil
}

func main() {
    addr := os.Getenv("GRPC_ADDR")
    if addr == "" {
        addr = ":50051"
    }

    lis, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    // Server options
    opts := []grpc.ServerOption{
        grpc.KeepaliveParams(keepalive.ServerParameters{
            MaxConnectionIdle:     15 * time.Second,
            MaxConnectionAge:      30 * time.Second,
            MaxConnectionAgeGrace: 5 * time.Second,
            Time:                  5 * time.Second,
            Timeout:               1 * time.Second,
        }),
        grpc.MaxConcurrentStreams(100),
    }

    grpcServer := grpc.NewServer(opts...)

    // Register service
    userpb.RegisterUserServiceServer(grpcServer, &userServer{})

    // Register health check service
    healthServer := health.NewServer()
    healthServer.SetServingStatus("user.UserService", healthpb.HealthCheckResponse_SERVING)
    healthpb.RegisterHealthServer(grpcServer, healthServer)

    // Enable reflection (for grpcurl and debugging - disable in production)
    if os.Getenv("GRPC_REFLECTION") == "true" {
        reflection.Register(grpcServer)
    }

    // Graceful shutdown
    go func() {
        quit := make(chan os.Signal, 1)
        signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
        <-quit
        log.Println("Shutting down gRPC server...")
        grpcServer.GracefulStop()
    }()

    log.Printf("gRPC server listening on %s", addr)
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}
EOF

TLS Configuration

Generate certificates for mTLS:

mkdir -p /etc/grpc/certs

# Generate CA key and certificate
openssl genrsa -out /etc/grpc/certs/ca.key 4096
openssl req -new -x509 -days 3650 \
    -key /etc/grpc/certs/ca.key \
    -out /etc/grpc/certs/ca.crt \
    -subj "/CN=gRPC CA/O=MyOrg"

# Generate server key and CSR
openssl genrsa -out /etc/grpc/certs/server.key 2048
openssl req -new \
    -key /etc/grpc/certs/server.key \
    -out /etc/grpc/certs/server.csr \
    -subj "/CN=grpc.example.com/O=MyOrg"

# Sign server certificate with CA
openssl x509 -req -days 365 \
    -in /etc/grpc/certs/server.csr \
    -CA /etc/grpc/certs/ca.crt \
    -CAkey /etc/grpc/certs/ca.key \
    -CAcreateserial \
    -out /etc/grpc/certs/server.crt

sudo chmod 600 /etc/grpc/certs/*.key

Add TLS to the Go server:

// Load TLS credentials
creds, err := credentials.NewServerTLSFromFile(
    "/etc/grpc/certs/server.crt",
    "/etc/grpc/certs/server.key",
)
if err != nil {
    log.Fatalf("Failed to load TLS: %v", err)
}

opts := []grpc.ServerOption{
    grpc.Creds(creds),
    // ... other options
}

Streaming RPC Types

Test streaming with grpcurl:

# Install grpcurl
wget https://github.com/fullstorydev/grpcurl/releases/latest/download/grpcurl_linux_x86_64.tar.gz
tar -xzf grpcurl_linux_x86_64.tar.gz
sudo mv grpcurl /usr/local/bin/

# List services (requires reflection enabled)
grpcurl -plaintext localhost:50051 list

# Call unary RPC
grpcurl -plaintext \
    -d '{"id": "123"}' \
    localhost:50051 \
    user.UserService/GetUser

# Call with TLS
grpcurl \
    -cacert /etc/grpc/certs/ca.crt \
    -d '{"id": "123"}' \
    grpc.example.com:50051 \
    user.UserService/GetUser

# Server streaming (gets all users)
grpcurl -plaintext \
    -d '{"page_size": 10}' \
    localhost:50051 \
    user.UserService/ListUsers

Load Balancing with Envoy

# Install Envoy
sudo apt install apt-transport-https ca-certificates gnupg
echo "deb [signed-by=/usr/share/keyrings/getenvoy-keyring.gpg] https://deb.dl.getenvoy.io/public/deb/ubuntu $(lsb_release -cs) main" | \
    sudo tee /etc/apt/sources.list.d/getenvoy.list

# Or use Docker
docker pull envoyproxy/envoy:v1.29-latest
cat > /etc/envoy/grpc-proxy.yaml << 'EOF'
static_resources:
  listeners:
  - name: grpc_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 9090    # Envoy listens here

    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: AUTO
          stat_prefix: grpc_proxy
          route_config:
            name: grpc_route
            virtual_hosts:
            - name: grpc_backend
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: grpc_service
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: grpc_service
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}   # gRPC requires HTTP/2
    load_assignment:
      cluster_name: grpc_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 50051
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 50052    # Second gRPC instance
EOF

Health Checking

# Check gRPC health with grpc-health-probe
wget https://github.com/grpc-ecosystem/grpc-health-probe/releases/latest/download/grpc_health_probe-linux-amd64
sudo mv grpc_health_probe-linux-amd64 /usr/local/bin/grpc-health-probe
sudo chmod +x /usr/local/bin/grpc-health-probe

# Check health
grpc-health-probe -addr=localhost:50051
# output: status: SERVING

# Check specific service
grpc-health-probe -addr=localhost:50051 -service=user.UserService

# With TLS
grpc-health-probe \
    -addr=grpc.example.com:50051 \
    -tls \
    -tls-ca-cert=/etc/grpc/certs/ca.crt

systemd Service Configuration

sudo tee /etc/systemd/system/grpc-user-service.service << 'EOF'
[Unit]
Description=gRPC User Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=grpcapp
Group=grpcapp
WorkingDirectory=/opt/mygrpc

Environment=GRPC_ADDR=:50051
Environment=GRPC_REFLECTION=false
EnvironmentFile=/etc/grpc/config.env

ExecStart=/opt/mygrpc/bin/server
ExecReload=/bin/kill -s HUP $MAINPID

Restart=on-failure
RestartSec=5s

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/log/grpc

LimitNOFILE=65536

StandardOutput=journal
StandardError=journal
SyslogIdentifier=grpc-user-service

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now grpc-user-service.service

Troubleshooting

"transport: Error while dialing dial tcp: connection refused":

# Check server is listening
ss -tlnp | grep 50051
sudo systemctl status grpc-user-service.service
journalctl -u grpc-user-service.service -n 30

TLS handshake failures:

# Verify certificate validity
openssl x509 -in /etc/grpc/certs/server.crt -noout -text | grep -E "Subject|Not"

# Test TLS connection
openssl s_client -connect grpc.example.com:50051 -CAfile /etc/grpc/certs/ca.crt

# Check client is using same CA
grpcurl -cacert /etc/grpc/certs/ca.crt -v localhost:50051 list

"Unimplemented" error for methods:

# The server must embed UnimplementedXxxServer for forward compatibility
# Check your server struct embeds:
# type myServer struct {
#     userpb.UnimplementedUserServiceServer
# }

High latency or timeouts:

# Check keepalive settings match between client and server
# Server MaxConnectionIdle should be > client keepalive interval
# Enable gRPC verbose logging
export GRPC_GO_LOG_VERBOSITY_LEVEL=99
export GRPC_GO_LOG_SEVERITY_LEVEL=info
./server

Conclusion

Deploying gRPC services in production requires careful attention to TLS configuration for secure communication, health checks for orchestration platforms like Kubernetes, and keepalive settings to handle long-lived connections. Envoy provides production-grade load balancing and protocol translation (gRPC-Web for browsers), while the standard gRPC health checking protocol integrates cleanly with both Kubernetes liveness probes and Envoy's health detection. Use grpcurl for testing and grpc-health-probe for automated health verification.