Repository: TwiN/gatus

Issue:
ICMP not working on Kubernetes even if sysctl -w net.ipv4.ping_group_range="0 2147483647"
### Describe the bug

Ping not working even if sysctl -w net.ipv4.ping_group_range="0 2147483647" is configured.
net.ipv4.ping_group_range is set in initContainer which have privileged role an run as root.

I tried to mount an alpine container in same pod where gatus is. I can successfully use ping in this alpine container which have same securitycontext as gatus container, is running a non root user, on uid and guid 65534 (nobody) 

I tried to ping google.com

Used config in config.yaml
endpoints:
  - name: TEST
    enabled: true
    url: "icmp://google.com"
    interval: 60s
    conditions:
    - "[CONNECTED] == true"
    client:
      timeout: 30s

### What do you see?

Endpoint is returning not ok

### What do you expect to see?

Ping success

### List the steps that must be taken to reproduce this issue

Use icmp on endpoint running in Kubernetes using this endpoints config
endpoints:
  - name: TEST
    enabled: true
    url: "icmp://google.com"
    interval: 60s
    conditions:
    - "[CONNECTED] == true"
    client:
      timeout: 30s
      
Use initContainer with root privlege using alpine image and execute this command:
 sysctl -w net.ipv4.ping_group_range="0 2147483647"

### Version

twinproduction/gatus:v5.7.0

### Additional information

_No response_

Discussion:
@kevin7s-io: I'm seeing the same behavior. I toyed around with passing ``sysctls`` to the ``securityContext`` and the method described by @jerome-karabenli -- both are unable to ping outside the pod. 

I've search existing issues (#633, #182, #105) and I'm wondering if I'm missing something,@h3mmy: The issue is here https://github.com/TwiN/gatus/blob/master/client/client.go#L246

`pinger.SetPrivileged(runtime.GOOS != "darwin")`

This will set privileged to true on linux and need to use the privileged ping instead of the unprivileged one. See https://github.com/prometheus-community/pro-bing/blob/ac3b40f1f0a7438a429e9bf6f2bc2a94ba286e39/ping.go#L430

 Linux and darwin both support NonPrivileged ping (https://pkg.go.dev/golang.org/x/net/icmp?utm_source=godoc#example-PacketConn-NonPrivilegedPing) so I would expect it to be safe to only filter for windows.

The change was made here: https://github.com/TwiN/gatus/commit/c423afb0bf87d0e1be2f73fec25b5199acf1aed7 for issue #132 but darwin supports non-privileged pings so the windows only condition _should_ be okay. 

@TwiN: Feel free to make a PR if you think that'll fix it!@TwiN: I created #748 in an attempt to address it, but I would appreciate if somebody (either @jerome-karabenli, @kevin7s-io, @h3mmy, @heathcliff26 or anybody reading this) could test it on their end and report back on whether #748 fixed it.

I've just [built a container image](https://github.com/TwiN/gatus/actions/runs/8871059906); if you'd like to try it, pull `twinproduction/gatus:experimental`.

Note that the image in question is only built for `linux/amd64`.@TwiN: Works on Windows, but not on my Kubernetes cluster, even with the following configuration on the pods
```
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              add:
                - NET_RAW
```

https://github.com/influxdata/influxdata-docker/pull/550 and https://github.com/influxdata/influxdata-docker/issues/547 seems to have some information on what needs to be done to fix this.

Looking at https://github.com/containerd/containerd/issues/6924, perhaps this will be fixed automagically too for Kubernetes 🤔 @heathcliff26: I have tested it with podman and the `experimental` image works when running as root, but not in rootless mode.

I also tested running `v5.10.0` as root since i didn't before, but it did not work.

So i guess the fix works, but still needs to have some privileges set.@joryirving: I'm currently experiencing this issue, where my config that was working in docker doesn't work in Kubernetes.

I tried a bunch of things, such as `capabilities`, and using the same SC I use in blackbox-exporter:
```
podSecurityContext:
  sysctls:
    - name: net.ipv4.ping_group_range
      value: "0 65536"
```

which also didn't work. I already have set
```
enable_unprivileged_ports = true
enable_unprivileged_icmp = true
```
and that doesn't appear to help either.@h3mmy: > I created #748 in an attempt to address it, but I would appreciate if somebody (either @jerome-karabenli, @kevin7s-io, @h3mmy, @heathcliff26 or anybody reading this) could test it on their end and report back on whether #748 fixed it.
> 
> I've just [built a container image](https://github.com/TwiN/gatus/actions/runs/8871059906); if you'd like to try it, pull `twinproduction/gatus:experimental`.
> 
> Note that the image in question is only built for `linux/amd64`.

SO sorry I missed this. I went ahead an tested the branch in #748 and **it works** in my k3s cluster.

This is a link to my HelmRelease: https://github.com/h3mmy/bloopySphere/blob/96329ee8e913168f11198920db4cd0f758b1ea68/cluster/apps/monitoring/gatus/app/helm-release.yaml

Important bits:

- container running as non-root
- dropped ALL capabilities
- disallow privilege escalation
- I do have an annotation to set the sysctls

> annotations:
>   reloader.stakater.com/auto: "true"
>    \# https://github.com/prometheus-community/pro-bing#linux
>    security.alpha.kubernetes.io/sysctls: net.ipv4.ping_group_range=0 2147483647

And the config I used as a test case: https://github.com/h3mmy/bloopySphere/blob/96329ee8e913168f11198920db4cd0f758b1ea68/cluster/apps/networking/traefik/external-services/nas-camelus.yaml

> ---
> apiVersion: v1
> kind: ConfigMap
> metadata:
>   name: camelus-plexii-gatus-ep
>   namespace: networking
>   labels:
>     gatus.io/enabled: "true"
> data:
>   config.yaml: |
>     endpoints:
>       - name: camelus-plexii-ping
>         group: infrastructure
>         url: icmp://${NAS_ADDRESS}
>         interval: 5m
>        ui:
>          hide-url: true
>          hide-hostname: true
>         conditions:
>          - "[CONNECTED] == true"
>         alerts:
>           - type: discord

Let me know if you'd like me to try any different arrangements for different scenarios.@joryirving: The experimental image resolved it for me. I didn't need the annotation.@h3mmy: > The experimental image resolved it for me. I didn't need the annotation.

It may vary with host distribution and kernel security profiles. I'm not an expert though.

Do you have any security profiles enabled on your host? AppArmor, seccomp, SELinux, etc? @joryirving: I do not.@TwiN: The following pod security context (pod, not container) fixed the issue for me:
```yaml
      securityContext:
        sysctls:
          - name: net.ipv4.ping_group_range
            value: 0 65536
```
Downside is that this fix is Kubernetes-specific, and releasing this as-is would break people deploying Gatus on Docker.
@joryirving: Just wanted to revisit this, as I realized I was still on the experimental image.
I tried swapping to 5.12, but IMCP still isn't working. I've tried the `securityContext` but that isn't resolving it either.

I have the following on the pod:
```yaml
            securityContext:
              allowPrivilegeEscalation: false
              readOnlyRootFilesystem: true
              capabilities: { drop: ["ALL"] }
              sysctls:
              - name: net.ipv4.ping_group_range
                value: 0 65536
```

and the following `defaultPodOptions`:
```yaml
    defaultPodOptions:
      dnsConfig:
        options:
          - { name: ndots, value: "1" }
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        runAsGroup: 65534
        fsGroup: 65534
        fsGroupChangePolicy: OnRootMismatch
        seccompProfile: { type: RuntimeDefault }
```

I've tried putting the `systctls` in the `defaultPodOptions.securityContext` and commenting out the `seccompProfile`, but that hasn't worked either.
@enorasec: I'm facing the same issue. I pulled the latest image (5.13.1) and configured it the sysctls. It's still not working for me. I set up the dnsutils container from k8s registry to ensure this would work on my kubernetes environment (Oracle Kubernetes Engine) with another container. I was able to show it being denied and then allowing with the sysctl securityContexts. 

I also noticed when I set an endpoint to external ping (GitHub.com); it was successful with `https://` but not with `icmp://`@rafaelorafaelo: Hi, same problem here with plain vanilla RHEL/Rocky 9 installation and podman running as root.
My findings:

- `net.ipv4.ping_group_range` is properly set on RHEL/Rocky
- Gatus works with podman option `--privileged` [man podman-run](https://docs.podman.io/en/stable/markdown/podman-run.1.html)
- simple Alpine container `podman run alpine ping -c3 google.com` works, ping runs as root (`podman run alpine whoami`)
- running ping in a container without root privileges of the internal container user ((in my case zabbix-agent2 with user zabbix `podman exec -it zabbix-agent2 ping -c3 google.com`) doesn't work. The error message is ```
ping: socktype: SOCK_RAW
ping: socket: Operation not permitted
ping: => missing cap_net_raw+p capability or setuid?```
- running ping in the same container (zabbix-agent2) started with `--privileged` parameter works
- starting podman with added capability `--cap-add=CAP_NET_RAW` works for Gatus ICMP check and Zabbix Agent 2 ping@thequailman: I opened a PR with a fix for this issue.  You can try it out by git cloning my branch (`git clone -b bugfix/add-net-cap-raw https://github.com/thequailman/gatus`, running `docker build . -t gatus`, and using `gatus` as your image.@h3mmy: > Just wanted to revisit this, as I realized I was still on the experimental image. I tried swapping to 5.12, but IMCP still isn't working. I've tried the `securityContext` but that isn't resolving it either.
> 
> I have the following on the pod:
> 
>             securityContext:
>               allowPrivilegeEscalation: false
>               readOnlyRootFilesystem: true
>               capabilities: { drop: ["ALL"] }
>               sysctls:
>               - name: net.ipv4.ping_group_range
>                 value: 0 65536
> 
> and the following `defaultPodOptions`:
> 
>     defaultPodOptions:
>       dnsConfig:
>         options:
>           - { name: ndots, value: "1" }
>       securityContext:
>         runAsNonRoot: true
>         runAsUser: 65534
>         runAsGroup: 65534
>         fsGroup: 65534
>         fsGroupChangePolicy: OnRootMismatch
>         seccompProfile: { type: RuntimeDefault }
> 
> I've tried putting the `systctls` in the `defaultPodOptions.securityContext` and commenting out the `seccompProfile`, but that hasn't worked either.

I tried 5.17.x but that also doesn't work even with the correct sysctls. I'm still using the experimental tag as that lets me use icmp. 

@joryirving have you tried adding CAP_NET_RAW to the capabilities? It feels like it shouldn't be necessary but the code in the fix is specifically requiring it. @joryirving: TBH I removed it as a requirement, and pivoted my ICMP requests into blackbox_exporter.@PatrickHuetter: I also couldn't get it running using https://artifacthub.io/packages/helm/twin/gatus helm chart and the mentioned securityContext settings. But what worked: Using the **experimental** docker image tag (with the mentioned securityContext settings).@enorasec: I know this is closed but just want to another anecdote. 

- I was unable to run v5.18.1 unless the container was run as root. v5.18.0 runs perfectly fine for me with the NET_RAW in the container build.
- I tried setting the `podSecurityContext` with sysctl mentioned above
    - Made sure my kubernetes hosts' sysctl settings were set with the same settings.
    - Also tried putting the default sysctl in the CRI-O config similar to [here](https://github.com/cri-o/cri-o/issues/4476)
- Added `securityContext` settings with `NET_RAW` and `NET_ADMIN` capabilities with no effect while as non-root.  I verified the container was receiving `NET_RAW` verifying it through `crictl inspect <containerID>`. Regardless, if the container security context was set with capabilities or not. 
- 

Environment info
Oracle Kubernetes Engine w/ managed nodes using Oracle Linux 8.10
Kubernetes Version:  v1.33.1
CRI-O Version: 1.33.0-41.1de604ccee4.el8@TwiN: @enorasec while this issue is closed, I'm still open to somebody submitting a fix for it. The only reason I rolled back the previous "fix" is that while it fixed the issue for some people/environments, it also caused breaking changes for others, and I personally prioritize stability over features.

Ultimately, causing breaking changes for thousands of devs/companies is not something I want Gatus to be known for, which means that while I too want this issue fixed for those affected, I'm not willing to break the status quo to get there 😆

That said, thank you for the context. I'm sure that if somebody tries to give fixing this issue another go, they'll benefit from your comment.@enorasec: @TwiN  - fully understandable. Providing some more information if anyone came by was the intention. Thinking about it, was it ever tested if you filtered for the privileged ping only on windows instead of just darwin? @h3mmy mentioned it about a year ago in the [comment](https://github.com/TwiN/gatus/issues/697#issuecomment-2002586093)
@h3mmy: > @TwiN  - fully understandable. Providing some more information if anyone came by was the intention. Thinking about it, was it ever tested if you filtered for the privileged ping only on windows instead of just darwin? @h3mmy mentioned it about a year ago in the [comment](https://github.com/TwiN/gatus/issues/697#issuecomment-2002586093)
> 

I tested the experimental image here: https://github.com/TwiN/gatus/issues/697#issuecomment-2141077524

It worked for my use case with container not running as root and without NET RAW or ADMIN capabilities. 

You can test with the "experimental" tag if you want but I think there is concern about this being a breaking change for people running the container with elevated privileges potentially needing it to be run as root. In that case the correct thing to do would be to check runtime privileges before applying the filter. I'm happy to work on that PR if there's interest. I just need to carve out some time in my schedule which might take a couple of weeks. @h3mmy: 2 weeks turned into some months. Sorry, main workplace really got me down. I'm catching up on the pile of things across different projects.

@TwiN checking individual capabilities will require adding `"kernel.org/pub/linux/libs/security/libcap/cap"` as a dependency. I'm not sure if you want to add more dependencies for a small check, so I can use some naive logic to check if the app is running as root like checking the `EUID == 0` 

Since SetPrivileged needs to be set to false for non-privileged processes running on linux or darwin, I figured this is a reasonable check unless you are wanting a more precise check with the extra dependency. As of 5.26.0, ICMP checks still do not work for my (non-root) deployment regardless of whether I add CAP_NET_RAW @TwiN: @h3mmy I'm open to giving a shot to your approach. I'll gladly re-publish a test image under a custom tag to try it on my end.

It's certainly one of those trickier issues, as pings/icmp checks are pretty much the most important feature an uptime monitoring application should support.

It's a little selfish, but personally, so long as it doesn't break my 2 use cases (Gatus on Kubernetes (linux)) and Gatus on my local machine, I'm open to merging the change (after I tried it on my end) and letting it soak on `latest` for a few days to see what happens.@h3mmy: I completely understand the desire to not break existing use cases, especially personal use ones. I have some time this weekend and would be happy to work through any issues that we can identify. @a-bali: Sorry to come back to this, but it is not clear to me, how do I fix the issue with pings in K8s? I run Gatus via its Helm chart in my Talos cluster and ICMP pings fail. I added the below config to the helm chart (and the required privileged pod-security labels to the namespace) but it doesn't help:

```
securityContext:
    capabilities:
      add:
        - NET_RAW
podSecurityContext:
    sysctls:
      - name: net.ipv4.ping_group_range
        value: 0 2147483647
```@TwiN: @a-bali The most recent attempt at addressing this is #1346.

Long story short, there doesn't appear to be a single solution that works for every platform/environment right now, and I personally prioritize not making breaking changes to existing environments.

I haven't had time to look into this after my last attempt (see the comments in this issue), but I'm hoping the aforementioned PR will fix it once I have time to test things on my end.@a-bali: Got it, thanks. I confirm that the version in #1346 works fine without any additional config.

Solution patch:
diff --git a/README.md b/README.md
index 0a9d13dd..467dd9f7 100644
--- a/README.md
+++ b/README.md
@@ -373,13 +373,13 @@ Where:
   - Using the example configuration above, the key would be `core_ext-ep-test`.
 - `{success}` is a boolean (`true` or `false`) value indicating whether the health check was successful or not.
 - `{error}` (optional): a string describing the reason for a failed health check. If {success} is false, this should contain the error message; if the check is successful.
-- `{duration}` (optional): the time that the request took as a duration string (e.g. 10s). 
+- `{duration}` (optional): the time that the request took as a duration string (e.g. 10s).
 
 You must also pass the token as a `Bearer` token in the `Authorization` header.
 
 
 ### Suites (ALPHA)
-Suites are collections of endpoints that are executed sequentially with a shared context. 
+Suites are collections of endpoints that are executed sequentially with a shared context.
 This allows you to create complex monitoring scenarios where the result from one endpoint can be used in subsequent endpoints, enabling workflow-style monitoring.
 
 Here are a few cases in which suites could be useful:
@@ -421,7 +421,7 @@ suites:
     context:
       price: "19.99"  # Initial static value in context
     endpoints:
-      # Step 1: Create an item and store the item ID 
+      # Step 1: Create an item and store the item ID
       - name: create-item
         url: https://api.example.com/items
         method: POST
@@ -435,7 +435,7 @@ suites:
         alerts:
           - type: slack
             description: "Failed to create item"
-            
+
       # Step 2: Update the item using the stored item ID
       - name: update-item
         url: https://api.example.com/items/[CONTEXT].itemId
@@ -446,7 +446,7 @@ suites:
         alerts:
           - type: slack
             description: "Failed to update item"
-        
+
       # Step 3: Fetch the item and validate the price
       - name: get-item
         url: https://api.example.com/items/[CONTEXT].itemId
@@ -457,7 +457,7 @@ suites:
         alerts:
           - type: slack
             description: "Item price did not update correctly"
-            
+
       # Step 4: Delete the item (always-run: true to ensure cleanup even if step 2 or 3 fails)
       - name: delete-item
         url: https://api.example.com/items/[CONTEXT].itemId
@@ -536,7 +536,7 @@ System-wide announcements allow you to display important messages at the top of
 
 Types:
 - **outage**: Indicates service disruptions or critical issues (red theme)
-- **warning**: Indicates potential issues or important notices (yellow theme)  
+- **warning**: Indicates potential issues or important notices (yellow theme)
 - **information**: General information or updates (blue theme)
 - **operational**: Indicates resolved issues or normal operations (green theme)
 - **none**: Neutral announcements with no specific severity (gray theme, default if none are specified)
@@ -548,7 +548,7 @@ announcements:
     type: outage
     message: "Scheduled maintenance on database servers from 14:00 to 16:00 UTC"
   - timestamp: 2025-08-15T16:15:00Z
-    type: operational  
+    type: operational
     message: "Database maintenance completed successfully. All systems operational."
   - timestamp: 2025-08-15T12:00:00Z
     type: information
@@ -709,7 +709,7 @@ endpoints:
 > 📝 Note that if running in a container, you must volume mount the certificate and key into the container.
 
 ### Tunneling
-Gatus supports SSH tunneling to monitor internal services through jump hosts or bastion servers. 
+Gatus supports SSH tunneling to monitor internal services through jump hosts or bastion servers.
 This is particularly useful for monitoring services that are not directly accessible from where Gatus is deployed.
 
 SSH tunnels are defined globally in the `tunneling` section and then referenced by name in endpoint client configurations.
@@ -746,7 +746,7 @@ endpoints:
       - "[STATUS] == 200"
 ```
 
-> ⚠️ **WARNING**:: Tunneling may introduce additional latency, especially if the connection to the tunnel is retried frequently. 
+> ⚠️ **WARNING**:: Tunneling may introduce additional latency, especially if the connection to the tunnel is retried frequently.
 > This may lead to inaccurate response time measurements.
 
 
@@ -2182,7 +2182,7 @@ Here's an example of what the notifications look like:
 | `alerting.telegram`                   | Configuration for alerts of type `telegram`                                                | `{}`                       |
 | `alerting.telegram.token`             | Telegram Bot Token                                                                         | Required `""`              |
 | `alerting.telegram.id`                | Telegram User ID                                                                           | Required `""`              |
-| `alerting.telegram.topic-id`          | Telegram Topic ID in a group corresponds to `message_thread_id` in the Telegram API        | `""`                       |    
+| `alerting.telegram.topic-id`          | Telegram Topic ID in a group corresponds to `message_thread_id` in the Telegram API        | `""`                       |
 | `alerting.telegram.api-url`           | Telegram API URL                                                                           | `https://api.telegram.org` |
 | `alerting.telegram.client`            | Client configuration. <br />See [Client configuration](#client-configuration).             | `{}`                       |
 | `alerting.telegram.default-alert`     | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert) | N/A                        |
@@ -2844,7 +2844,7 @@ will send a `POST` request to `http://localhost:8080/playground` with the follow
 
 
 ### Recommended interval
-To ensure that Gatus provides reliable and accurate results (i.e. response time), Gatus limits the number of 
+To ensure that Gatus provides reliable and accurate results (i.e. response time), Gatus limits the number of
 endpoints/suites that can be evaluated at the same time.
 In other words, even if you have multiple endpoints with the same interval, they are not guaranteed to run at the same time.
 
@@ -2952,8 +2952,8 @@ endpoints:
 ```
 
 The `[BODY]` placeholder contains the output of the query, and `[CONNECTED]`
-shows whether the connection was successfully established. You can use Go template 
-syntax. 
+shows whether the connection was successfully established. You can use Go template
+syntax.
 
 
 ### Monitoring an endpoint using ICMP
@@ -2970,7 +2970,7 @@ endpoints:
 Only the placeholders `[CONNECTED]`, `[IP]` and `[RESPONSE_TIME]` are supported for endpoints of type ICMP.
 You can specify a domain prefixed by `icmp://`, or an IP address prefixed by `icmp://`.
 
-If you run Gatus on Linux, please read the Linux section on https://github.com/prometheus-community/pro-bing#linux
+If you run Gatus on Linux, please read the Linux section on [https://github.com/prometheus-community/pro-bing#linux]
 if you encounter any problems.
 
 
@@ -3088,7 +3088,7 @@ endpoints:
       - "[CERTIFICATE_EXPIRATION] > 240h"
 ```
 
-> ⚠ The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to use RDAP, or as a fallback, send a request to the official IANA WHOIS service 
+> ⚠ The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to use RDAP, or as a fallback, send a request to the official IANA WHOIS service
 > [through a library](https://github.com/TwiN/whois) and in some cases, a secondary request to a TLD-specific WHOIS server (e.g. `whois.nic.sh`).
 > To prevent the WHOIS service from throttling your IP address if you send too many requests, Gatus will prevent you from
 > using the `[DOMAIN_EXPIRATION]` placeholder on an endpoint with an interval of less than `5m`.
@@ -3117,7 +3117,7 @@ concurrency: 0
 
 **Use cases for higher concurrency:**
 - You have a large number of endpoints to monitor
-- You want to monitor endpoints at very short intervals (< 5s)  
+- You want to monitor endpoints at very short intervals (< 5s)
 - You're using Gatus for load testing scenarios
 
 **Legacy configuration:**
@@ -3201,7 +3201,7 @@ ui:
   default-sort-by: group
 ```
 Note that if a user has already sorted the dashboard by a different field, the default sort will not be applied unless the user
-clears their browser's localstorage. 
+clears their browser's localstorage.
 
 
 ### Exposing Gatus on a custom path
diff --git a/client/client.go b/client/client.go
index 81df0d2a..52ed5baf 100644
--- a/client/client.go
+++ b/client/client.go
@@ -13,6 +13,7 @@ import (
 	"net"
 	"net/http"
 	"net/smtp"
+	"os"
 	"runtime"
 	"strings"
 	"time"
@@ -343,12 +344,7 @@ func Ping(address string, config *Config) (bool, time.Duration) {
 	pinger := ping.New(address)
 	pinger.Count = 1
 	pinger.Timeout = config.Timeout
-	// Set the pinger's privileged mode to true for every GOOS except darwin
-	// See https://github.com/TwiN/gatus/issues/132
-	//
-	// Note that for this to work on Linux, Gatus must run with sudo privileges.
-	// See https://github.com/prometheus-community/pro-bing#linux
-	pinger.SetPrivileged(runtime.GOOS != "darwin")
+	pinger.SetPrivileged(ShouldRunPingerAsPrivileged())
 	pinger.SetNetwork(config.Network)
 	err := pinger.Run()
 	if err != nil {
@@ -364,6 +360,25 @@ func Ping(address string, config *Config) (bool, time.Duration) {
 	return true, 0
 }
 
+// ShouldRunPingerAsPrivileged will determine whether or not to run pinger in privileged mode.
+// It should be set to privileged when running as root, and always on windows. See https://pkg.go.dev/github.com/macrat/go-parallel-pinger#Pinger.SetPrivileged
+func ShouldRunPingerAsPrivileged() bool {
+	// Set the pinger's privileged mode to false for darwin
+	// See https://github.com/TwiN/gatus/issues/132
+	// linux should also be set to false, but there are potential complications
+	// See https://github.com/TwiN/gatus/pull/748 and https://github.com/TwiN/gatus/issues/697#issuecomment-2081700989
+	//
+	// Note that for this to work on Linux, Gatus must run with sudo privileges. (in certain cases)
+	// See https://github.com/prometheus-community/pro-bing#linux
+	if runtime.GOOS == "windows" {
+		return true
+	}
+	// To actually check for cap_net_raw capabilities, we would need to add "kernel.org/pub/linux/libs/security/libcap/cap" to gatus.
+	// Or use a syscall and check for permission errors, but this requires platform specific compilation
+	// As a backstop we can simply check the effective user id and run as privileged when running as root
+	return os.Geteuid() == 0
+}
+
 // QueryWebSocket opens a websocket connection, write `body` and return a message from the server
 func QueryWebSocket(address, body string, headers map[string]string, config *Config) (bool, []byte, error) {
 	const (



Test patch:
diff --git a/client/client_test.go b/client/client_test.go
index c381d410..f041bfe5 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -6,6 +6,8 @@ import (
 	"io"
 	"net/http"
 	"net/netip"
+	"os"
+	"runtime"
 	"testing"
 	"time"
 
@@ -129,6 +131,33 @@ func TestPing(t *testing.T) {
 	}
 }
 
+func TestShouldRunPingerAsPrivileged(t *testing.T) {
+	// Don't run in parallel since we're testing system-dependent behavior
+	if runtime.GOOS == "windows" {
+		result := ShouldRunPingerAsPrivileged()
+		if !result {
+			t.Error("On Windows, ShouldRunPingerAsPrivileged() should return true")
+		}
+		return
+	}
+
+	// Non-Windows tests
+	result := ShouldRunPingerAsPrivileged()
+	isRoot := os.Geteuid() == 0
+
+	// Test cases based on current environment
+	if isRoot {
+		if !result {
+			t.Error("When running as root, ShouldRunPingerAsPrivileged() should return true")
+		}
+	} else {
+		// When not root, the result depends on raw socket creation
+		// We can at least verify the function runs without panic
+		t.Logf("Non-root privileged result: %v", result)
+	}
+}
+
+
 func TestCanPerformStartTLS(t *testing.T) {
 	type args struct {
 		address     string