diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6568167749..c474b2fb0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
   Because of this change, you now need `use std::iter::Extend` to call `extend`
   on a `SigSet`.
   (#[1553](https://github.com/nix-rust/nix/pull/1553))
+- Removed the the `PATH_MAX` restriction from APIs accepting paths. Paths
+  will now be allocated on the heap if they are too long. In addition, large
+  instruction count improvements (~30x) were made to path handling.
 
 ### Fixed
 
diff --git a/src/lib.rs b/src/lib.rs
index 0cfd3fb4e8..6809382978 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -156,19 +156,11 @@ feature! {
 #[allow(missing_docs)]
 pub mod unistd;
 
-/*
- *
- * ===== Result / Error =====
- *
- */
-
-use libc::PATH_MAX;
-
-use std::{ptr, result, slice};
-use std::ffi::{CStr, OsStr};
+use std::ffi::{CStr, CString, OsStr};
 use std::mem::MaybeUninit;
 use std::os::unix::ffi::OsStrExt;
 use std::path::{Path, PathBuf};
+use std::{ptr, result, slice};
 
 use errno::Errno;
 
@@ -242,12 +234,9 @@ impl NixPath for CStr {
     }
 
     fn with_nix_path<T, F>(&self, f: F) -> Result<T>
-            where F: FnOnce(&CStr) -> T {
-        // Equivalence with the [u8] impl.
-        if self.len() >= PATH_MAX as usize {
-            return Err(Errno::ENAMETOOLONG)
-        }
-
+    where
+        F: FnOnce(&CStr) -> T,
+    {
         Ok(f(self))
     }
 }
@@ -265,11 +254,19 @@ impl NixPath for [u8] {
     where
         F: FnOnce(&CStr) -> T,
     {
-        if self.len() >= PATH_MAX as usize {
-            return Err(Errno::ENAMETOOLONG);
+        // The real PATH_MAX is typically 4096, but it's statistically unlikely to have a path
+        // longer than ~300 bytes. See the the PR description to get stats for your own machine.
+        // https://github.com/nix-rust/nix/pull/1656
+        //
+        // By being smaller than a memory page, we also avoid the compiler inserting a probe frame:
+        // https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html
+        const MAX_STACK_ALLOCATION: usize = 1024;
+
+        if self.len() >= MAX_STACK_ALLOCATION {
+            return with_nix_path_allocating(self, f);
         }
 
-        let mut buf = MaybeUninit::<[u8; PATH_MAX as usize]>::uninit();
+        let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
         let buf_ptr = buf.as_mut_ptr() as *mut u8;
 
         unsafe {
@@ -284,6 +281,18 @@ impl NixPath for [u8] {
     }
 }
 
+#[cold]
+#[inline(never)]
+fn with_nix_path_allocating<T, F>(from: &[u8], f: F) -> Result<T>
+where
+    F: FnOnce(&CStr) -> T,
+{
+    match CString::new(from) {
+        Ok(s) => Ok(f(&s)),
+        Err(_) => Err(Errno::EINVAL),
+    }
+}
+
 impl NixPath for Path {
     fn is_empty(&self) -> bool {
         NixPath::is_empty(self.as_os_str())