Repository: prometheus/blackbox_exporter
Pull request #835 (2021-10-13 14:49:02)

Add support for grpc health check
Hi, This PR is to add support for [gRPC health check](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) to blackbox exporter.
I've read the discussion on issues #665 and #603, it seems that gRPC is out of scope for blackbox_exporter, if this is still the case just close the PR.

averzicco: great, I've checked the dev summit video and realized didn't implement the logic to specify the service name for the health check. I've just pushed the code which takes care of that

Patch:
diff --git a/README.md b/README.md
index 95dee65..6658f7e 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,33 @@ scrape_configs:
         replacement: 127.0.0.1:9115  # The blackbox exporter's real hostname:port.
 ```
 
+HTTP probes can accept an additional `hostname` parameter that will set `Host` header and TLS SNI. This can be especially useful with `dns_sd_config`:
+```yaml
+scrape_configs:
+  - job_name: blackbox_all
+    metrics_path: /probe
+    params:
+      module: [ http_2xx ]  # Look for a HTTP 200 response.
+    dns_sd_configs:
+      - names:
+          - example.com
+          - prometheus.io
+        type: A
+        port: 443
+    relabel_configs:
+      - source_labels: [__address__]
+        target_label: __param_target
+        replacement: https://$1/  # Make probe URL be like https://1.2.3.4:443/
+      - source_labels: [__param_target]
+        target_label: instance
+      - target_label: __address__
+        replacement: 127.0.0.1:9115  # The blackbox exporter's real hostname:port.
+      - source_labels: [__meta_dns_name]
+        target_label: __param_hostname  # Make domain name become 'Host' header for probe requests
+      - source_labels: [__meta_dns_name]
+        target_label: vhost  # and store it in 'vhost' label
+```
+
 ## Permissions
 
 The ICMP probe requires elevated privileges to function:
diff --git a/blackbox.yml b/blackbox.yml
index 65c97cc..8418399 100644
--- a/blackbox.yml
+++ b/blackbox.yml
@@ -15,6 +15,16 @@ modules:
       tls: true
       tls_config:
         insecure_skip_verify: false
+  grpc:
+    prober: grpc
+    grpc:
+      tls: true
+      preferred_ip_protocol: "ip4"
+  grpc_plain:
+    prober: grpc
+    grpc:
+      tls: false
+      service: "service1"
   ssh_banner:
     prober: tcp
     tcp:
diff --git a/config/config.go b/config/config.go
index 3e246bc..e36b47c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -63,6 +63,12 @@ var (
 		HTTPClientConfig:   config.DefaultHTTPClientConfig,
 	}
 
+	// DefaultGRPCProbe set default value for HTTPProbe
+	DefaultGRPCProbe = GRPCProbe{
+		Service:            "",
+		IPProtocolFallback: true,
+	}
+
 	// DefaultTCPProbe set default value for TCPProbe
 	DefaultTCPProbe = TCPProbe{
 		IPProtocolFallback: true,
@@ -188,6 +194,7 @@ type Module struct {
 	TCP     TCPProbe      `yaml:"tcp,omitempty"`
 	ICMP    ICMPProbe     `yaml:"icmp,omitempty"`
 	DNS     DNSProbe      `yaml:"dns,omitempty"`
+	GRPC    GRPCProbe     `yaml:"grpc,omitempty"`
 }
 
 type HTTPProbe struct {
@@ -211,6 +218,14 @@ type HTTPProbe struct {
 	BodySizeLimit                units.Base2Bytes        `yaml:"body_size_limit,omitempty"`
 }
 
+type GRPCProbe struct {
+	Service             string           `yaml:"service,omitempty"`
+	TLS                 bool             `yaml:"tls,omitempty"`
+	TLSConfig           config.TLSConfig `yaml:"tls_config,omitempty"`
+	IPProtocolFallback  bool             `yaml:"ip_protocol_fallback,omitempty"`
+	PreferredIPProtocol string           `yaml:"preferred_ip_protocol,omitempty"`
+}
+
 type HeaderMatch struct {
 	Header       string `yaml:"header,omitempty"`
 	Regexp       Regexp `yaml:"regexp,omitempty"`
@@ -320,6 +335,16 @@ func (s *HTTPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	return nil
 }
 
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *GRPCProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	*s = DefaultGRPCProbe
+	type plain GRPCProbe
+	if err := unmarshal((*plain)(s)); err != nil {
+		return err
+	}
+	return nil
+}
+
 // UnmarshalYAML implements the yaml.Unmarshaler interface.
 func (s *DNSProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	*s = DefaultDNSProbe
diff --git a/go.mod b/go.mod
index befc41d..3700613 100644
--- a/go.mod
+++ b/go.mod
@@ -8,9 +8,10 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_model v0.2.0
-	github.com/prometheus/common v0.31.1
+	github.com/prometheus/common v0.32.1
 	github.com/prometheus/exporter-toolkit v0.7.0
-	golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
+	golang.org/x/net v0.0.0-20211020060615-d418f374d309
+	google.golang.org/grpc v1.41.0
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index e3defb0..03d1989 100644
--- a/go.sum
+++ b/go.sum
@@ -43,6 +43,7 @@ github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a h1:E/8AP5dFtMhl5K
 github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
 github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
 github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -55,13 +56,18 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -126,8 +132,10 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -180,8 +188,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
-github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/exporter-toolkit v0.7.0 h1:XtYeVeeC5daG4txbc9+mieKq+/AK4gtIBLl9Mulrjnk=
 github.com/prometheus/exporter-toolkit v0.7.0/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@@ -189,6 +197,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -197,8 +206,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -207,6 +218,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -275,8 +287,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -435,11 +447,13 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -453,6 +467,10 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -474,11 +492,13 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/main.go b/main.go
index 971aed9..0c5a14f 100644
--- a/main.go
+++ b/main.go
@@ -67,6 +67,7 @@ var (
 		"tcp":  prober.ProbeTCP,
 		"icmp": prober.ProbeICMP,
 		"dns":  prober.ProbeDNS,
+		"grpc": prober.ProbeGRPC,
 	}
 
 	moduleUnknownCounter = prometheus.NewCounter(prometheus.CounterOpts{
@@ -120,6 +121,15 @@ func probeHandler(w http.ResponseWriter, r *http.Request, c *config.Config, logg
 		return
 	}
 
+	hostname := params.Get("hostname")
+	if module.Prober == "http" && hostname != "" {
+		err = setHTTPHost(hostname, &module)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
+	}
+
 	sl := newScrapeLogger(logger, moduleName, target)
 	level.Info(sl).Log("msg", "Beginning probe", "probe", module.Prober, "timeout_seconds", timeoutSeconds)
 
@@ -150,6 +160,23 @@ func probeHandler(w http.ResponseWriter, r *http.Request, c *config.Config, logg
 	h.ServeHTTP(w, r)
 }
 
+func setHTTPHost(hostname string, module *config.Module) error {
+	// By creating a new hashmap and copying values there we
+	// ensure that the initial configuration remain intact.
+	headers := make(map[string]string)
+	if module.HTTP.Headers != nil {
+		for name, value := range module.HTTP.Headers {
+			if strings.Title(name) == "Host" && value != hostname {
+				return fmt.Errorf("host header defined both in module configuration (%s) and with URL-parameter 'hostname' (%s)", value, hostname)
+			}
+			headers[name] = value
+		}
+	}
+	headers["Host"] = hostname
+	module.HTTP.Headers = headers
+	return nil
+}
+
 type scrapeLogger struct {
 	next         log.Logger
 	buffer       bytes.Buffer
diff --git a/prober/grpc.go b/prober/grpc.go
new file mode 100644
index 0000000..c528597
--- /dev/null
+++ b/prober/grpc.go
@@ -0,0 +1,219 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package prober
+
+import (
+	"context"
+	"github.com/go-kit/log"
+	"github.com/go-kit/log/level"
+	"github.com/prometheus/blackbox_exporter/config"
+	"github.com/prometheus/client_golang/prometheus"
+	pconfig "github.com/prometheus/common/config"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/health/grpc_health_v1"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/grpc/status"
+	"net"
+	"net/url"
+	"strings"
+	"time"
+)
+
+type GRPCHealthCheck interface {
+	Check(c context.Context, service string) (bool, codes.Code, *peer.Peer, string, error)
+}
+
+type gRPCHealthCheckClient struct {
+	client grpc_health_v1.HealthClient
+	conn   *grpc.ClientConn
+}
+
+func NewGrpcHealthCheckClient(conn *grpc.ClientConn) GRPCHealthCheck {
+	client := new(gRPCHealthCheckClient)
+	client.client = grpc_health_v1.NewHealthClient(conn)
+	client.conn = conn
+	return client
+}
+
+func (c *gRPCHealthCheckClient) Close() error {
+	return c.conn.Close()
+}
+
+func (c *gRPCHealthCheckClient) Check(ctx context.Context, service string) (bool, codes.Code, *peer.Peer, string, error) {
+	var res *grpc_health_v1.HealthCheckResponse
+	var err error
+	req := grpc_health_v1.HealthCheckRequest{
+		Service: service,
+	}
+
+	serverPeer := new(peer.Peer)
+	res, err = c.client.Check(ctx, &req, grpc.Peer(serverPeer))
+	if err == nil {
+		if res.GetStatus() == grpc_health_v1.HealthCheckResponse_SERVING {
+			return true, codes.OK, serverPeer, res.Status.String(), nil
+		}
+		return false, codes.OK, serverPeer, res.Status.String(), nil
+	}
+
+	returnStatus, _ := status.FromError(err)
+
+	return false, returnStatus.Code(), nil, "", err
+}
+
+func ProbeGRPC(ctx context.Context, target string, module config.Module, registry *prometheus.Registry, logger log.Logger) (success bool) {
+
+	var (
+		durationGaugeVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: "probe_grpc_duration_seconds",
+			Help: "Duration of gRPC request by phase",
+		}, []string{"phase"})
+
+		isSSLGauge = prometheus.NewGauge(prometheus.GaugeOpts{
+			Name: "probe_grpc_ssl",
+			Help: "Indicates if SSL was used for the connection",
+		})
+
+		statusCodeGauge = prometheus.NewGauge(prometheus.GaugeOpts{
+			Name: "probe_grpc_status_code",
+			Help: "Response gRPC status code",
+		})
+
+		healthCheckResponseGaugeVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: "probe_grpc_healthcheck_response",
+			Help: "Response HealthCheck response",
+		}, []string{"serving_status"})
+
+		probeSSLEarliestCertExpiryGauge = prometheus.NewGauge(prometheus.GaugeOpts{
+			Name: "probe_ssl_earliest_cert_expiry",
+			Help: "Returns earliest SSL cert expiry in unixtime",
+		})
+
+		probeTLSVersion = prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: "probe_tls_version_info",
+			Help: "Contains the TLS version used",
+		},
+			[]string{"version"},
+		)
+	)
+
+	for _, lv := range []string{"resolve"} {
+		durationGaugeVec.WithLabelValues(lv)
+	}
+
+	registry.MustRegister(durationGaugeVec)
+	registry.MustRegister(isSSLGauge)
+	registry.MustRegister(statusCodeGauge)
+	registry.MustRegister(healthCheckResponseGaugeVec)
+	registry.MustRegister(probeSSLEarliestCertExpiryGauge)
+	registry.MustRegister(probeTLSVersion)
+
+	if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") {
+		target = "http://" + target
+	}
+
+	targetURL, err := url.Parse(target)
+	if err != nil {
+		level.Error(logger).Log("msg", "Could not parse target URL", "err", err)
+		return false
+	}
+
+	targetHost, targetPort, err := net.SplitHostPort(targetURL.Host)
+	// If split fails, assuming it's a hostname without port part.
+	if err != nil {
+		targetHost = targetURL.Host
+	}
+
+	tlsConfig, err := pconfig.NewTLSConfig(&module.GRPC.TLSConfig)
+	if err != nil {
+		level.Error(logger).Log("msg", "Error creating TLS configuration", "err", err)
+		return false
+	}
+
+	ip, lookupTime, err := chooseProtocol(ctx, module.GRPC.PreferredIPProtocol, module.GRPC.IPProtocolFallback, targetHost, registry, logger)
+	if err != nil {
+		level.Error(logger).Log("msg", "Error resolving address", "err", err)
+		return false
+	}
+	durationGaugeVec.WithLabelValues("resolve").Add(lookupTime)
+	checkStart := time.Now()
+	if len(tlsConfig.ServerName) == 0 {
+		// If there is no `server_name` in tls_config, use
+		// the hostname of the target.
+		tlsConfig.ServerName = targetHost
+	}
+
+	if targetPort == "" {
+		targetURL.Host = "[" + ip.String() + "]"
+	} else {
+		targetURL.Host = net.JoinHostPort(ip.String(), targetPort)
+	}
+
+	var opts []grpc.DialOption
+	target = targetHost + ":" + targetPort
+	if !module.GRPC.TLS {
+		level.Debug(logger).Log("msg", "Dialing GRPC without TLS")
+		opts = append(opts, grpc.WithInsecure())
+		if len(targetPort) == 0 {
+			target = targetHost + ":80"
+		}
+	} else {
+		creds := credentials.NewTLS(tlsConfig)
+		opts = append(opts, grpc.WithTransportCredentials(creds))
+		if len(targetPort) == 0 {
+			target = targetHost + ":443"
+		}
+	}
+
+	conn, err := grpc.Dial(target, opts...)
+
+	if err != nil {
+		level.Error(logger).Log("did not connect: %v", err)
+	}
+
+	client := NewGrpcHealthCheckClient(conn)
+	defer conn.Close()
+	ok, statusCode, serverPeer, servingStatus, err := client.Check(context.Background(), module.GRPC.Service)
+	durationGaugeVec.WithLabelValues("check").Add(time.Since(checkStart).Seconds())
+
+	for servingStatusName, _ := range grpc_health_v1.HealthCheckResponse_ServingStatus_value {
+		healthCheckResponseGaugeVec.WithLabelValues(servingStatusName).Set(float64(0))
+	}
+	if servingStatus != "" {
+		healthCheckResponseGaugeVec.WithLabelValues(servingStatus).Set(float64(1))
+	}
+
+	if serverPeer != nil {
+		tlsInfo, tlsOk := serverPeer.AuthInfo.(credentials.TLSInfo)
+		if tlsOk {
+			isSSLGauge.Set(float64(1))
+			probeSSLEarliestCertExpiryGauge.Set(float64(getEarliestCertExpiry(&tlsInfo.State).Unix()))
+			probeTLSVersion.WithLabelValues(getTLSVersion(&tlsInfo.State)).Set(1)
+		} else {
+			isSSLGauge.Set(float64(0))
+		}
+	}
+	statusCodeGauge.Set(float64(statusCode))
+
+	if !ok || err != nil {
+		level.Error(logger).Log("msg", "can't connect grpc server:", "err", err)
+		success = false
+	} else {
+		level.Debug(logger).Log("connect the grpc server successfully")
+		success = true
+	}
+
+	return
+}
diff --git a/prober/http.go b/prober/http.go
index fe73082..156feca 100644
--- a/prober/http.go
+++ b/prober/http.go
@@ -344,6 +344,15 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
 		// If there is no `server_name` in tls_config, use
 		// the hostname of the target.
 		httpClientConfig.TLSConfig.ServerName = targetHost
+
+		// However, if there is a Host header it is better to use
+		// its value instead. This helps avoid TLS handshake error
+		// if targetHost is an IP address.
+		for name, value := range httpConfig.Headers {
+			if strings.Title(name) == "Host" {
+				httpClientConfig.TLSConfig.ServerName = value
+			}
+		}
 	}
 	client, err := pconfig.NewClientFromConfig(httpClientConfig, "http_probe", pconfig.WithKeepAlivesDisabled())
 	if err != nil {


Test patch:
diff --git a/main_test.go b/main_test.go
index ee2d6d8..e7ba5c6 100644
--- a/main_test.go
+++ b/main_test.go
@@ -15,6 +15,7 @@ package main
 
 import (
 	"bytes"
+	"fmt"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -190,3 +191,68 @@ func TestComputeExternalURL(t *testing.T) {
 		}
 	}
 }
+
+func TestHostnameParam(t *testing.T) {
+	headers := map[string]string{}
+	c := &config.Config{
+		Modules: map[string]config.Module{
+			"http_2xx": config.Module{
+				Prober:  "http",
+				Timeout: 10 * time.Second,
+				HTTP: config.HTTPProbe{
+					Headers:            headers,
+					IPProtocolFallback: true,
+				},
+			},
+		},
+	}
+
+	// check that 'hostname' parameter make its way to Host header
+	hostname := "foo.example.com"
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Host != hostname {
+			t.Errorf("Unexpected Host: expected %q, got %q.", hostname, r.Host)
+		}
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer ts.Close()
+
+	requrl := fmt.Sprintf("?debug=true&hostname=%s&target=%s", hostname, ts.URL)
+
+	req, err := http.NewRequest("GET", requrl, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rr := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		probeHandler(w, r, c, log.NewNopLogger(), &resultHistory{})
+	})
+
+	handler.ServeHTTP(rr, req)
+
+	if status := rr.Code; status != http.StatusOK {
+		t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusOK)
+	}
+
+	// check that ts got the request to perform header check
+	if !strings.Contains(rr.Body.String(), "probe_success 1") {
+		t.Errorf("probe failed, response body: %v", rr.Body.String())
+	}
+
+	// check that host header both in config and in parameter will result in 400
+	c.Modules["http_2xx"].HTTP.Headers["Host"] = hostname + ".something"
+
+	handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		probeHandler(w, r, c, log.NewNopLogger(), &resultHistory{})
+	})
+
+	rr = httptest.NewRecorder()
+	handler.ServeHTTP(rr, req)
+
+	if status := rr.Code; status != http.StatusBadRequest {
+		t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusBadRequest)
+	}
+}
diff --git a/prober/grpc_test.go b/prober/grpc_test.go
new file mode 100644
index 0000000..acb0513
--- /dev/null
+++ b/prober/grpc_test.go
@@ -0,0 +1,416 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package prober
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"github.com/go-kit/log"
+	"github.com/prometheus/blackbox_exporter/config"
+	"github.com/prometheus/client_golang/prometheus"
+	pconfig "github.com/prometheus/common/config"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
+	"io/ioutil"
+	"net"
+	"os"
+	"testing"
+	"time"
+)
+
+func TestGRPCConnection(t *testing.T) {
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+	s := grpc.NewServer()
+	healthServer := health.NewServer()
+	healthServer.SetServingStatus("service", grpc_health_v1.HealthCheckResponse_SERVING)
+	grpc_health_v1.RegisterHealthServer(s, healthServer)
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registry := prometheus.NewRegistry()
+
+	result := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+		},
+		}, registry, log.NewNopLogger())
+
+	if !result {
+		t.Fatalf("GRPC probe failed")
+	}
+
+	mfs, err := registry.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedMetrics := map[string]map[string]map[string]struct{}{
+		"probe_grpc_healthcheck_response": {
+			"serving_status": {
+				"UNKNOWN":         {},
+				"SERVING":         {},
+				"NOT_SERVING":     {},
+				"SERVICE_UNKNOWN": {},
+			},
+		},
+	}
+
+	checkMetrics(expectedMetrics, mfs, t)
+
+	expectedResults := map[string]float64{
+		"probe_grpc_ssl":         0,
+		"probe_grpc_status_code": 0,
+	}
+
+	checkRegistryResults(expectedResults, mfs, t)
+}
+
+func TestMultipleGRPCservices(t *testing.T) {
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+	s := grpc.NewServer()
+	healthServer := health.NewServer()
+	healthServer.SetServingStatus("service1", grpc_health_v1.HealthCheckResponse_SERVING)
+	healthServer.SetServingStatus("service2", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
+	grpc_health_v1.RegisterHealthServer(s, healthServer)
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registryService1 := prometheus.NewRegistry()
+
+	resultService1 := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+			Service:            "service1",
+		},
+		}, registryService1, log.NewNopLogger())
+
+	if !resultService1 {
+		t.Fatalf("GRPC probe failed for service1")
+	}
+
+	registryService2 := prometheus.NewRegistry()
+	resultService2 := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+			Service:            "service2",
+		},
+		}, registryService2, log.NewNopLogger())
+
+	if resultService2 {
+		t.Fatalf("GRPC probe succeed for service2")
+	}
+
+	registryService3 := prometheus.NewRegistry()
+	resultService3 := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+			Service:            "service3",
+		},
+		}, registryService3, log.NewNopLogger())
+
+	if resultService3 {
+		t.Fatalf("GRPC probe succeed for service3")
+	}
+}
+
+func TestGRPCTLSConnection(t *testing.T) {
+
+	certExpiry := time.Now().AddDate(0, 0, 1)
+	testCertTmpl := generateCertificateTemplate(certExpiry, false)
+	testCertTmpl.IsCA = true
+	_, testcertPem, testKey := generateSelfSignedCertificate(testCertTmpl)
+
+	// CAFile must be passed via filesystem, use a tempfile.
+	tmpCaFile, err := ioutil.TempFile("", "cafile.pem")
+	if err != nil {
+		t.Fatalf("Error creating CA tempfile: %s", err)
+	}
+	if _, err = tmpCaFile.Write(testcertPem); err != nil {
+		t.Fatalf("Error writing CA tempfile: %s", err)
+	}
+	if err = tmpCaFile.Close(); err != nil {
+		t.Fatalf("Error closing CA tempfile: %s", err)
+	}
+	defer os.Remove(tmpCaFile.Name())
+
+	testKeyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(testKey)})
+	testcert, err := tls.X509KeyPair(testcertPem, testKeyPem)
+	if err != nil {
+		panic(fmt.Sprintf("Failed to decode TLS testing keypair: %s\n", err))
+	}
+
+	tlsConfig := &tls.Config{
+		Certificates: []tls.Certificate{testcert},
+		MinVersion:   tls.VersionTLS12,
+		MaxVersion:   tls.VersionTLS12,
+	}
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+
+	s := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
+	healthServer := health.NewServer()
+	healthServer.SetServingStatus("service", grpc_health_v1.HealthCheckResponse_SERVING)
+	grpc_health_v1.RegisterHealthServer(s, healthServer)
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registry := prometheus.NewRegistry()
+
+	result := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			TLS:                true,
+			TLSConfig:          pconfig.TLSConfig{InsecureSkipVerify: true},
+			IPProtocolFallback: false,
+		},
+		}, registry, log.NewNopLogger())
+
+	if !result {
+		t.Fatalf("GRPC probe failed")
+	}
+
+	mfs, err := registry.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedLabels := map[string]map[string]string{
+		"probe_tls_version_info": {
+			"version": "TLS 1.2",
+		},
+	}
+	checkRegistryLabels(expectedLabels, mfs, t)
+
+	expectedResults := map[string]float64{
+		"probe_grpc_ssl":         1,
+		"probe_grpc_status_code": 0,
+	}
+
+	checkRegistryResults(expectedResults, mfs, t)
+}
+
+func TestNoTLSConnection(t *testing.T) {
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+	s := grpc.NewServer()
+	healthServer := health.NewServer()
+	healthServer.SetServingStatus("service", grpc_health_v1.HealthCheckResponse_SERVING)
+	grpc_health_v1.RegisterHealthServer(s, healthServer)
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registry := prometheus.NewRegistry()
+
+	result := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			TLS:                true,
+			TLSConfig:          pconfig.TLSConfig{InsecureSkipVerify: true},
+			IPProtocolFallback: false,
+		},
+		}, registry, log.NewNopLogger())
+
+	if result {
+		t.Fatalf("GRPC probe succeed")
+	}
+
+	mfs, err := registry.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedResults := map[string]float64{
+		"probe_grpc_ssl":         0,
+		"probe_grpc_status_code": 14, //UNAVAILABLE
+	}
+
+	checkRegistryResults(expectedResults, mfs, t)
+
+}
+
+func TestGRPCServiceNotFound(t *testing.T) {
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+	s := grpc.NewServer()
+	healthServer := health.NewServer()
+	healthServer.SetServingStatus("service", grpc_health_v1.HealthCheckResponse_SERVING)
+	grpc_health_v1.RegisterHealthServer(s, healthServer)
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registry := prometheus.NewRegistry()
+
+	result := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+			Service:            "NonExistingService",
+		},
+		}, registry, log.NewNopLogger())
+
+	if result {
+		t.Fatalf("GRPC probe succeed")
+	}
+
+	mfs, err := registry.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedResults := map[string]float64{
+		"probe_grpc_ssl":         0,
+		"probe_grpc_status_code": 5, //NOT_FOUND
+	}
+
+	checkRegistryResults(expectedResults, mfs, t)
+}
+
+func TestGRPCHealthCheckUnimplemented(t *testing.T) {
+
+	ln, err := net.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Fatalf("Error listening on socket: %s", err)
+	}
+	defer ln.Close()
+
+	_, port, err := net.SplitHostPort(ln.Addr().String())
+	if err != nil {
+		t.Fatalf("Error retrieving port for socket: %s", err)
+	}
+	s := grpc.NewServer()
+
+	go func() {
+		if err := s.Serve(ln); err != nil {
+			t.Errorf("failed to serve: %v", err)
+			return
+		}
+	}()
+	defer s.GracefulStop()
+
+	testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	registry := prometheus.NewRegistry()
+
+	result := ProbeGRPC(testCTX, "localhost:"+port,
+		config.Module{Timeout: time.Second, GRPC: config.GRPCProbe{
+			IPProtocolFallback: false,
+			Service:            "NonExistingService",
+		},
+		}, registry, log.NewNopLogger())
+
+	if result {
+		t.Fatalf("GRPC probe succeed")
+	}
+
+	mfs, err := registry.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectedResults := map[string]float64{
+		"probe_grpc_ssl":         0,
+		"probe_grpc_status_code": 12, //UNIMPLEMENTED
+	}
+
+	checkRegistryResults(expectedResults, mfs, t)
+}