diff --git a/src/lib/zlib/Error.cxx b/src/lib/zlib/Error.cxx
deleted file mode 100644
index f09273ddc..000000000
--- a/src/lib/zlib/Error.cxx
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: BSD-2-Clause
-// author: Max Kellermann <max.kellermann@gmail.com>
-
-#include "Error.hxx"
-
-#include <zlib.h>
-
-const char *
-ZlibError::what() const noexcept
-{
-	return zError(code);
-}
diff --git a/src/lib/zlib/Error.hxx b/src/lib/zlib/Error.hxx
index 745222037..9633a8d0b 100644
--- a/src/lib/zlib/Error.hxx
+++ b/src/lib/zlib/Error.hxx
@@ -3,17 +3,25 @@
 
 #pragma once
 
-#include <exception>
+#include <zlib.h>
 
-class ZlibError final : public std::exception {
-	int code;
+#include <system_error>
 
+class ZlibErrorCategory final : public std::error_category {
 public:
-	explicit ZlibError(int _code) noexcept:code(_code) {}
-
-	int GetCode() const noexcept {
-		return code;
+	const char *name() const noexcept override {
+		return "zlib";
 	}
 
-	const char *what() const noexcept override;
+	std::string message(int condition) const override {
+		return zError(condition);
+	}
 };
+
+inline ZlibErrorCategory zlib_error_category;
+
+inline std::system_error
+MakeZlibError(int code, const char *msg) noexcept
+{
+	return std::system_error(code, zlib_error_category, msg);
+}
diff --git a/src/lib/zlib/GunzipReader.cxx b/src/lib/zlib/GunzipReader.cxx
index 75bb38805..46c068187 100644
--- a/src/lib/zlib/GunzipReader.cxx
+++ b/src/lib/zlib/GunzipReader.cxx
@@ -15,7 +15,7 @@ GunzipReader::GunzipReader(Reader &_next)
 
 	int result = inflateInit2(&z, 16 + MAX_WBITS);
 	if (result != Z_OK)
-		throw ZlibError(result);
+		throw MakeZlibError(result, "inflateInit2() failed");
 }
 
 inline bool
@@ -60,7 +60,7 @@ GunzipReader::Read(std::span<std::byte> dest)
 			eof = true;
 			return dest.size() - z.avail_out;
 		} else if (result != Z_OK)
-			throw ZlibError(result);
+			throw MakeZlibError(result, "inflate() failed");
 
 		buffer.Consume(r.size() - z.avail_in);
 
diff --git a/src/lib/zlib/GzipOutputStream.cxx b/src/lib/zlib/GzipOutputStream.cxx
index 6d5fa1a79..5e4c83801 100644
--- a/src/lib/zlib/GzipOutputStream.cxx
+++ b/src/lib/zlib/GzipOutputStream.cxx
@@ -20,7 +20,7 @@ GzipOutputStream::GzipOutputStream(OutputStream &_next)
 				  windowBits | gzip_encoding,
 				  8, Z_DEFAULT_STRATEGY);
 	if (result != Z_OK)
-		throw ZlibError(result);
+		throw MakeZlibError(result, "deflateInit2() failed");
 }
 
 GzipOutputStream::~GzipOutputStream() noexcept
@@ -42,7 +42,7 @@ GzipOutputStream::SyncFlush()
 
 		int result = deflate(&z, Z_SYNC_FLUSH);
 		if (result != Z_OK)
-			throw ZlibError(result);
+			throw MakeZlibError(result, "deflate() failed");
 
 		if (z.next_out == output)
 			break;
@@ -70,7 +70,7 @@ GzipOutputStream::Finish()
 		if (result == Z_STREAM_END)
 			break;
 		else if (result != Z_OK)
-			throw ZlibError(result);
+			throw MakeZlibError(result, "deflate() failed");
 	}
 }
 
@@ -90,7 +90,7 @@ GzipOutputStream::Write(std::span<const std::byte> src)
 
 		int result = deflate(&z, Z_NO_FLUSH);
 		if (result != Z_OK)
-			throw ZlibError(result);
+			throw MakeZlibError(result, "deflate() failed");
 
 		if (z.next_out > output)
 			next.Write(std::as_bytes(std::span{output}.first(z.next_out - output)));
diff --git a/src/lib/zlib/meson.build b/src/lib/zlib/meson.build
index 6ca5645e2..d04dac1f9 100644
--- a/src/lib/zlib/meson.build
+++ b/src/lib/zlib/meson.build
@@ -6,7 +6,6 @@ endif
 
 zlib = static_library(
   'zlib',
-  'Error.cxx',
   'GunzipReader.cxx',
   'GzipOutputStream.cxx',
   'AutoGunzipReader.cxx',