diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9783251d65..bf7da41155 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -738,6 +738,23 @@ jobs:
             g++ -Werror include/*.h
             clang -Werror -x c++-header include/*.h
 
+  headers:
+    name: "Self-contained headers"
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v5
+
+      - name: Test script
+        run: |
+          ./tools/check-headers.sh \
+            src/field.h \
+            src/field_5x52.h \
+            src/field_10x26.h \
+            src/hash.h \
+            src/hash_impl.h
+
   sage:
     name: "SageMath prover"
     runs-on: ubuntu-latest
diff --git a/src/field.h b/src/field.h
index 1f6ba7460f..f4b13f4b20 100644
--- a/src/field.h
+++ b/src/field.h
@@ -29,13 +29,6 @@
  * implementation also provides a secp256k1_fe_verify routine to verify that
  * these fields match the run-time value and perform internal consistency
  * checks. */
-#ifdef VERIFY
-#  define SECP256K1_FE_VERIFY_FIELDS \
-    int magnitude; \
-    int normalized;
-#else
-#  define SECP256K1_FE_VERIFY_FIELDS
-#endif
 
 #if defined(SECP256K1_WIDEMUL_INT128)
 #include "field_5x52.h"
diff --git a/src/field_10x26.h b/src/field_10x26.h
index 203c10167c..c01d92c10b 100644
--- a/src/field_10x26.h
+++ b/src/field_10x26.h
@@ -30,7 +30,10 @@ typedef struct {
      *     sum(i=0..9, n[i] << (i*26)) < p
      *     (together these imply n[9] <= 2^22 - 1)
      */
-    SECP256K1_FE_VERIFY_FIELDS
+#ifdef VERIFY
+    int magnitude;
+    int normalized;
+#endif
 } secp256k1_fe;
 
 /* Unpacks a constant into a overlapping multi-limbed FE element. */
diff --git a/src/field_5x52.h b/src/field_5x52.h
index f20c246fdd..30cdf3bb10 100644
--- a/src/field_5x52.h
+++ b/src/field_5x52.h
@@ -30,7 +30,10 @@ typedef struct {
      *     sum(i=0..4, n[i] << (i*52)) < p
      *     (together these imply n[4] <= 2^48 - 1)
      */
-    SECP256K1_FE_VERIFY_FIELDS
+#ifdef VERIFY
+    int magnitude;
+    int normalized;
+#endif
 } secp256k1_fe;
 
 /* Unpacks a constant into a overlapping multi-limbed FE element. */
diff --git a/tools/check-headers.sh b/tools/check-headers.sh
new file mode 100755
index 0000000000..a07472ce87
--- /dev/null
+++ b/tools/check-headers.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -eu
+
+builddir=$(mktemp -d)
+trap 'rm -rf "$builddir"' EXIT INT TERM
+
+for header in "$@"; do
+    source_file="${builddir}/${header%.h}.c"
+    object_file="${builddir}/${header%.h}.o"
+    mkdir -p "$(dirname "$source_file")"
+    cp "$header" "$source_file"
+
+    cc -I include -I src -c "$source_file" -o "$object_file"
+    exit_code=$?
+    if [ $exit_code -ne 0 ]; then
+        exit $exit_code
+    fi
+
+    echo "$header... OK"
+done