diff --git a/secp256k1-sys/build.rs b/secp256k1-sys/build.rs
index 2d9f8441b..e08c77a99 100644
--- a/secp256k1-sys/build.rs
+++ b/secp256k1-sys/build.rs
@@ -25,14 +25,77 @@ extern crate cc;
 
 use std::env;
 
-fn main() {
-    // Actual build
+fn gen_max_align() {
+    configured_cc()
+        .file("depend/max_align.c")
+        .cargo_metadata(false)
+        .compile("max_align.o");
+    let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("missing OUT_DIR"));
+    let target_endian = std::env::var("CARGO_CFG_TARGET_ENDIAN")
+        .expect("missing CARGO_CFG_TARGET_ENDIAN");
+    let target_pointer_width_bytes = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
+        .expect("missing CARGO_CFG_TARGET_POINTER_WIDTH")
+        .parse::<usize>()
+        .expect("malformed CARGO_CFG_TARGET_POINTER_WIDTH")
+        // CARGO_CFG_TARGET_POINTER_WIDTH is in bits, we want bytes
+        / 8;
+    let max_align_bin = out_dir.join("max_align.bin");
+    // Note that this copies *whole* sections to a binary file.
+    // It's a bit brittle because some other symbol could theoretically end up there.
+    // Currently it only has one on my machine and we guard against unexpected changes by checking
+    // the size - it must match the target pointer width.
+    let objcopy = std::process::Command::new("objcopy")
+        .args(&["-O", "binary"])
+        // cc inserts depend - WTF
+        .arg(out_dir.join("depend/max_align.o"))
+        .arg(&max_align_bin)
+        .spawn()
+        .expect("failed to run objcopy")
+        .wait()
+        .expect("failed to wait for objcopy");
+    assert!(objcopy.success(), "objcopy failed");
+    let mut max_align_bytes = std::fs::read(max_align_bin).expect("failed to read max_align.bin");
+    // The `usize` of target and host may not match so we need to do conversion.
+    // Sensible alignments should be very small anyway but we don't want crappy `unsafe` code.
+    // Little endian happens to be a bit easier to process so we convert into that.
+    // If the type is smaller than `u64` we zero-pad it.
+    // If the type is larger than `u6` but the number fits into `u64` it'll have
+    // unused tail which is easy to cut-off.
+    // If the number is larger than `u64::MAX` then bytes beyond `u64` size will
+    // be non-zero.
+    //
+    // So as long as the max alignment fits into `u64` this can decode alignment
+    // for any architecture on any architecture.
+    assert_eq!(max_align_bytes.len(), target_pointer_width_bytes);
+    if target_endian != "little" {
+        max_align_bytes.reverse()
+    }
+    // copying like this auto-pads the number with zeroes
+    let mut buf = [0; std::mem::size_of::<u64>()];
+    let to_copy = buf.len().min(max_align_bytes.len());
+    // Overflow check
+    if max_align_bytes[to_copy..].iter().any(|b| *b != 0) {
+        panic!("max alignment overflowed u64");
+    }
+    buf[..to_copy].copy_from_slice(&max_align_bytes[..to_copy]);
+    let max_align = u64::from_le_bytes(buf);
+    let src = format!(r#"
+/// A type that is as aligned as the biggest alignment for fundamental types in C.
+///
+/// Since C11 that means as aligned as `max_align_t` is.
+/// The exact size/alignment is unspecified.
+#[repr(align({}))]
+#[derive(Default, Copy, Clone)]
+pub struct AlignedType([u8; {}]);"#, max_align, max_align);
+    std::fs::write(out_dir.join("aligned_type.rs"), src.as_bytes()).expect("failed to write aligned_type.rs");
+}
+
+/// Returns CC builder configured with all defines but no C files.
+fn configured_cc() -> cc::Build {
+    // While none of these currently affect max alignment we prefer to keep the "hygiene" so that
+    // new code will be correct.
     let mut base_config = cc::Build::new();
-    base_config.include("depend/secp256k1/")
-               .include("depend/secp256k1/include")
-               .include("depend/secp256k1/src")
-               .flag_if_supported("-Wno-unused-function") // some ecmult stuff is defined but not used upstream
-               .define("SECP256K1_API", Some(""))
+    base_config.define("SECP256K1_API", Some(""))
                .define("ENABLE_MODULE_ECDH", Some("1"))
                .define("ENABLE_MODULE_SCHNORRSIG", Some("1"))
                .define("ENABLE_MODULE_EXTRAKEYS", Some("1"));
@@ -48,6 +111,17 @@ fn main() {
     #[cfg(feature = "recovery")]
     base_config.define("ENABLE_MODULE_RECOVERY", Some("1"));
 
+    base_config
+}
+
+fn build_secp256k1() {
+    let mut base_config = configured_cc();
+    base_config.include("depend/secp256k1/")
+               .include("depend/secp256k1/include")
+               .include("depend/secp256k1/src")
+               .flag_if_supported("-Wno-unused-function"); // some ecmult stuff is defined but not used upstream
+
+
     // WASM headers and size/align defines.
     if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "wasm32" {
         base_config.include("wasm/wasm-sysroot")
@@ -69,3 +143,7 @@ fn main() {
     }
 }
 
+fn main() {
+    gen_max_align();
+    build_secp256k1();
+}
diff --git a/secp256k1-sys/depend/max_align.c b/secp256k1-sys/depend/max_align.c
new file mode 100644
index 000000000..521171803
--- /dev/null
+++ b/secp256k1-sys/depend/max_align.c
@@ -0,0 +1,5 @@
+#include <stddef.h>
+
+// Note that this symbol is NOT linked with the rest of the library.
+// The name is sort of unique in case it accidentally gets linked.
+const size_t rust_secp256k1_private_max_align = _Alignof(max_align_t);
diff --git a/secp256k1-sys/src/types.rs b/secp256k1-sys/src/types.rs
index e457ec41c..224f81e35 100644
--- a/secp256k1-sys/src/types.rs
+++ b/secp256k1-sys/src/types.rs
@@ -11,21 +11,15 @@ pub type c_char = i8;
 
 pub use core::ffi::c_void;
 
-/// A type that is as aligned as the biggest alignment for fundamental types in C
-/// since C11 that means as aligned as `max_align_t` is.
-/// the exact size/alignment is unspecified.
-// 16 matches is as big as the biggest alignment in any arch that rust currently supports https://github.com/rust-lang/rust/blob/2c31b45ae878b821975c4ebd94cc1e49f6073fd0/library/std/src/sys_common/alloc.rs
-#[repr(align(16))]
-#[derive(Default, Copy, Clone)]
-pub struct AlignedType([u8; 16]);
+include!(concat!(env!("OUT_DIR"), "/aligned_type.rs"));
 
 impl AlignedType {
     pub fn zeroed() -> Self {
-        AlignedType([0u8; 16])
+        Self::ZERO
     }
 
     /// A static zeroed out AlignedType for use in static assignments of [AlignedType; _]
-    pub const ZERO: AlignedType = AlignedType([0u8; 16]);
+    pub const ZERO: AlignedType = AlignedType([0u8; core::mem::size_of::<AlignedType>()]);
 }
 
 #[cfg(all(feature = "alloc", not(rust_secp_no_symbol_renaming)))]