diff --git a/Makefile b/Makefile
index 72a696b2a..d78d043df 100644
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,7 @@
 #   USE_OPENSSL_WOLFSSL     : enable use of wolfSSL with the OpenSSL API
 #   USE_QUIC                : enable use of QUIC with the quictls API (quictls, libressl, boringssl)
 #   USE_QUIC_OPENSSL_COMPAT : enable use of QUIC with the standard openssl API (limited features)
+#   USE_PKCS11              : enable support for PKCS#11 with BoringSSL and AWS-LC
 #   USE_ENGINE              : enable use of OpenSSL Engine.
 #   USE_LUA                 : enable Lua support.
 #   USE_ACCEPT4             : enable use of accept4() on linux. Automatic.
@@ -345,7 +346,8 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL                        \
            USE_THREAD_DUMP USE_EVPORTS USE_OT USE_QUIC USE_PROMEX             \
            USE_MEMORY_PROFILING                                               \
            USE_STATIC_PCRE USE_STATIC_PCRE2                                   \
-           USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT USE_QUIC_OPENSSL_COMPAT
+           USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT USE_QUIC_OPENSSL_COMPAT \
+           USE_PKCS11
 
 # preset all variables for all supported build options among use_opts
 $(reset_opts_vars)
@@ -632,6 +634,10 @@ ifneq ($(USE_OPENSSL:0=),)
   OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o     \
                   src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o              \
                   src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o
+  ifneq ($(USE_PKCS11:0=),)
+  OPTIONS_OBJS += src/pkcs11.o src/pkcs11-token.o src/pkcs11-uri.o                   \
+                  src/pkcs11-notify.o
+  endif
 endif
 
 ifneq ($(USE_ENGINE:0=),)
diff --git a/doc/internals/pkcs11.txt b/doc/internals/pkcs11.txt
new file mode 100644
index 000000000..89ff30e76
--- /dev/null
+++ b/doc/internals/pkcs11.txt
@@ -0,0 +1,37 @@
+The PKCS#11 support in HAProxy is designed around the 2.40 synchronous definition
+which requires operations to occur on separate worker threads.  Future
+specifications (3.2+) have yet to be standardised which support asynchronous
+operation.  Therefore, in order to use PKCS#11 within HAProxy a configured number
+of PKCS#11 worker threads have to be defined within the global section of the
+configuration.
+
+In order to shuttle the information between the HAProxy threads and the worker
+threads, requests to perform signing (or RSA X.509 decrypt) create a buffer to
+store the input and output data and then place these in a queue.  This is
+handled by the pkcs11_notify module which ensures that if the task is cancelled
+and freed that the buffers are not freed until the queued task is completed.
+
+Loading of PKCS#11 is performed in the same way as is supported for the latchset
+OpenSSL PKCS#11 engine and provider implementations.  This is through the use of
+a PKCS#11 Provider URI PEM file which takes the place of a PEM private key which
+would ordinarily be loaded into HAProxy.  Parsing of this URI is performed in
+the pkcs11_uri module which uses the specification from RFC7512.  Only information
+that is supported by our implementation is actually loaded from the URI and the
+rest is ignored.
+
+The pkcs11_token module performs the communication with the PKCS#11 library and
+manages the queue of signing (or decryption) operations on the worker threads.
+
+Finally, the pkcs11 module co-ordinates all of this to provide an interface to
+the ssl_chck and ssl_sock modules.  Currently this is through the use of a
+SSL_PRIVATE_KEY_METHOD exposed by BoringSSL and AWS-LC.  It would be easily
+possible to expose an OpenSSL engine for OpenSSL 1 and an OpenSSL provider for
+OpenSSL 3 to use the pkcs11_token module, but since there is already the latchset
+implementations this was not an immediate priority.  WolfTLS already supports
+PKCS#11 natively according to its documentation, but this has not been checked.
+
+To enable the PKCS#11 features the flag USE_PKCS11=1 should be passed to the
+Makefile.
+
+The implementation has been tested with the Google CloudHSM utilising the library
+provided at https://github.com/GoogleCloudPlatform/kms-integrations.
diff --git a/include/haproxy/pkcs11-notify.h b/include/haproxy/pkcs11-notify.h
new file mode 100644
index 000000000..e37411e84
--- /dev/null
+++ b/include/haproxy/pkcs11-notify.h
@@ -0,0 +1,60 @@
+/*
+ * include/haproxy/pkcs11-token.h
+ * PKCS#11 asynchronous task management.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_NOTIFY_H
+#define _HAPROXY_PKCS11_NOTIFY_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct pkcs11_notify;
+struct wait_event;
+
+/* create an empty pkcs11_notify */
+struct pkcs11_notify *pkcs11_notify_new(void);
+
+/* ready a notify structure to take a new pending output */
+int pkcs11_notify_alloc(struct pkcs11_notify *notify, size_t max_out);
+
+/* get the current buffer for a pending action */
+uint8_t *pkcs11_notify_buffer(struct pkcs11_notify *notify);
+
+/* get the size of the buffer for a pending action */
+size_t pkcs11_notify_size(struct pkcs11_notify *notify);
+
+/* complete a pending action */
+void pkcs11_notify_complete(struct pkcs11_notify *notify, size_t len);
+
+/* read a pending action and clear the notify, returns 0 on failure, -1 on
+ * pending and 1 on success
+ */
+int pkcs11_notify_clear(struct pkcs11_notify *notify,
+                        uint8_t *out, size_t *out_len, size_t max_len);
+
+/* wake a tasklet now if the notify is complete, or log the wake up for later
+ * if it's still pending
+ */
+void pkcs11_notify(struct pkcs11_notify *notify, struct wait_event *wait_event);
+
+/* free a pkcs11_notify structure */
+void pkcs11_notify_free(struct pkcs11_notify *notify);
+
+#endif /* _HAPROXY_PKCS11_NOTIFY_H */
diff --git a/include/haproxy/pkcs11-spec.h b/include/haproxy/pkcs11-spec.h
new file mode 100644
index 000000000..d6f22307f
--- /dev/null
+++ b/include/haproxy/pkcs11-spec.h
@@ -0,0 +1,264 @@
+/*
+ * include/haproxy/pkcs11-spec.h
+ * PKCS#11 module definition.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+ #ifndef _HAPROXY_PKCS11_SPEC_H
+ #define _HAPROXY_PKCS11_SPEC_H
+
+/* specification taken from
+ * https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html#_Toc416959679
+ */
+
+typedef unsigned char CK_BYTE;
+typedef CK_BYTE CK_CHAR;
+typedef CK_BYTE CK_UTF8CHAR;
+typedef CK_BYTE CK_BBOOL;
+typedef unsigned long int CK_ULONG;
+typedef long int CK_LONG;
+
+typedef CK_ULONG CK_FLAGS;
+typedef CK_ULONG CK_RV;
+typedef CK_ULONG CK_SESSION_HANDLE;
+typedef CK_ULONG CK_OBJECT_HANDLE;
+typedef CK_ULONG CK_ATTRIBUTE_TYPE;
+typedef CK_ULONG CK_MECHANISM_TYPE;
+typedef CK_ULONG CK_USER_TYPE;
+typedef CK_ULONG CK_SLOT_ID;
+typedef CK_ULONG CK_NOTIFICATION;
+typedef CK_ULONG CK_OBJECT_CLASS;
+
+typedef void* CK_VOID_PTR;
+typedef CK_VOID_PTR* CK_VOID_PTR_PTR;
+
+typedef struct CK_VERSION {
+	CK_BYTE major;
+	CK_BYTE minor;
+} CK_VERSION;
+
+typedef CK_RV(*CK_LOCKMUTEX)(CK_VOID_PTR pMutex);
+typedef CK_RV(*CK_CREATEMUTEX)(CK_VOID_PTR_PTR ppMutex);
+typedef CK_RV(*CK_DESTROYMUTEX)(CK_VOID_PTR ppMutex);
+typedef CK_RV(*CK_UNLOCKMUTEX)(CK_VOID_PTR ppMutex);
+
+typedef struct CK_C_INITIALIZE_ARGS {
+	CK_CREATEMUTEX CreateMutex;
+	CK_DESTROYMUTEX DestroyMutex;
+	CK_LOCKMUTEX LockMutex;
+	CK_UNLOCKMUTEX UnlockMutex;
+	CK_FLAGS flags;
+	CK_VOID_PTR pReserved;
+} CK_C_INITIALIZE_ARGS;
+
+typedef struct CK_INFO {
+	CK_VERSION cryptokiVersion;
+	CK_UTF8CHAR manufacturerID[32];
+	CK_FLAGS flags;
+	CK_UTF8CHAR libraryDescription[32];
+	CK_VERSION libraryVersion;
+} CK_INFO;
+
+typedef struct CK_ATTRIBUTE {
+	CK_ATTRIBUTE_TYPE type;
+	CK_VOID_PTR pValue;
+	CK_ULONG ulValueLen;
+} CK_ATTRIBUTE;
+
+typedef struct CK_MECHANISM {
+	CK_MECHANISM_TYPE mechanism;
+	CK_VOID_PTR pParameter;
+	CK_ULONG ulParameterLen;
+} CK_MECHANISM;
+
+typedef struct CK_SLOT_INFO {
+	CK_UTF8CHAR slotDescription[64];
+	CK_UTF8CHAR manufacturerID[32];
+	CK_FLAGS flags;
+	CK_VERSION hardwareVersion;
+	CK_VERSION firmwareVersion;
+} CK_SLOT_INFO;
+
+typedef struct CK_TOKEN_INFO {
+	CK_UTF8CHAR label[32];
+	CK_UTF8CHAR manufacturerID[32];
+	CK_UTF8CHAR model[16];
+	CK_CHAR serialNumber[16];
+	CK_FLAGS flags;
+	CK_ULONG ulMaxSessionCount;
+	CK_ULONG ulSessionCount;
+	CK_ULONG ulMaxRwSessionCount;
+	CK_ULONG ulRwSessionCount;
+	CK_ULONG ulMaxPinLen;
+	CK_ULONG ulMinPinLen;
+	CK_ULONG ulTotalPublicMemory;
+	CK_ULONG ulFreePublicMemory;
+	CK_ULONG ulTotalPrivateMemory;
+	CK_ULONG ulFreePrivateMemory;
+	CK_VERSION hardwareVersion;
+	CK_VERSION firmwareVersion;
+	CK_CHAR utcTime[16];
+} CK_TOKEN_INFO;
+
+typedef struct CK_MECHANISM_INFO {
+   CK_ULONG ulMinKeySize;
+   CK_ULONG ulMaxKeySize;
+   CK_FLAGS flags;
+} CK_MECHANISM_INFO;
+
+typedef struct CK_FUNCTION_LIST* CK_FUNCTION_LIST_PTR;
+
+typedef CK_RV(*CK_NOTIFY)(CK_SESSION_HANDLE hSession, CK_NOTIFICATION event, CK_VOID_PTR pApplication);
+
+typedef CK_RV(*CK_C_Initialize)(CK_C_INITIALIZE_ARGS* pInitArgs);
+typedef CK_RV(*CK_C_Finalize)(CK_VOID_PTR pReserved);
+typedef CK_RV(*CK_C_GetInfo)(CK_INFO* pInfo);
+typedef CK_RV(*CK_C_GetFunctionList)(CK_FUNCTION_LIST_PTR* ppFunctionList);
+typedef CK_RV(*CK_C_GetSlotList)(CK_BBOOL tokenPresent, CK_SLOT_ID *pSlotList, CK_ULONG *pulCount);
+typedef CK_RV(*CK_C_GetSlotInfo)(CK_SLOT_ID slotID, CK_SLOT_INFO *pInfo);
+typedef CK_RV(*CK_C_GetTokenInfo)(CK_SLOT_ID slotID, CK_TOKEN_INFO *pInfo);
+typedef CK_RV(*CK_C_OpenSession)(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, CK_NOTIFY Notify, CK_SESSION_HANDLE *phSession);
+typedef CK_RV(*CK_C_CloseSession)(CK_SESSION_HANDLE hSession);
+typedef CK_RV(*CK_C_GetMechanismList)(CK_SLOT_ID slotID, CK_MECHANISM_TYPE *pMechanismList, CK_ULONG *pulCount);
+typedef CK_RV(*CK_C_GetMechanismInfo)(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, CK_MECHANISM_INFO *pInfo);
+typedef CK_RV(*CK_C_Login)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR *pPin, CK_ULONG ulPinLen);
+typedef CK_RV(*CK_C_FindObjectsInit)(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE *pTemplate, CK_ULONG ulCount);
+typedef CK_RV(*CK_C_FindObjects)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE *phObject, CK_ULONG ulMaxObjectCount, CK_ULONG *pulObjectCount);
+typedef CK_RV(*CK_C_FindObjectsFinal)(CK_SESSION_HANDLE hSession);
+typedef CK_RV(*CK_C_DecryptInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM *pMechanism, CK_OBJECT_HANDLE hKey);
+typedef CK_RV(*CK_C_Decrypt)(CK_SESSION_HANDLE hSession, CK_BYTE *pEncryptedData, CK_ULONG ulEncryptedDataLen, CK_BYTE *pData, CK_ULONG *pulDataLen);
+typedef CK_RV(*CK_C_SignInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM *pMechanism, CK_OBJECT_HANDLE hKey);
+typedef CK_RV(*CK_C_Sign)(CK_SESSION_HANDLE hSession, CK_BYTE *pData, CK_ULONG ulDataLen, CK_BYTE *pSignature, CK_ULONG *pulSignatureLen);
+
+/* unused functions are simply defined as CK_VOID_PTR to avoid extra unused
+ * definitions.
+ */
+typedef struct CK_FUNCTION_LIST {
+	CK_VERSION version;
+	CK_C_Initialize C_Initialize;
+	CK_C_Finalize C_Finalize;
+	CK_C_GetInfo C_GetInfo;
+	CK_C_GetFunctionList C_GetFunctionList;
+	CK_C_GetSlotList C_GetSlotList;
+	CK_C_GetSlotInfo C_GetSlotInfo;
+	CK_C_GetTokenInfo C_GetTokenInfo;
+	CK_C_GetMechanismList C_GetMechanismList;
+	CK_C_GetMechanismInfo C_GetMechanismInfo;
+	CK_VOID_PTR C_InitToken;
+	CK_VOID_PTR C_InitPIN;
+	CK_VOID_PTR C_SetPIN;
+	CK_C_OpenSession C_OpenSession;
+	CK_C_CloseSession C_CloseSession;
+	CK_VOID_PTR C_CloseAllSessions;
+	CK_VOID_PTR C_GetSessionInfo;
+	CK_VOID_PTR C_GetOperationState;
+	CK_VOID_PTR C_SetOperationState;
+	CK_C_Login C_Login;
+	CK_VOID_PTR C_Logout;
+	CK_VOID_PTR C_CreateObject;
+	CK_VOID_PTR C_CopyObject;
+	CK_VOID_PTR C_DestroyObject;
+	CK_VOID_PTR C_GetObjectSize;
+	CK_VOID_PTR C_GetAttributeValue;
+	CK_VOID_PTR C_SetAttributeValue;
+	CK_C_FindObjectsInit C_FindObjectsInit;
+	CK_C_FindObjects C_FindObjects;
+	CK_C_FindObjectsFinal C_FindObjectsFinal;
+	CK_VOID_PTR C_EncryptInit;
+	CK_VOID_PTR C_Encrypt;
+	CK_VOID_PTR C_EncryptUpdate;
+	CK_VOID_PTR C_EncryptFinal;
+	CK_C_DecryptInit C_DecryptInit;
+	CK_C_Decrypt C_Decrypt;
+	CK_VOID_PTR C_DecryptUpdate;
+	CK_VOID_PTR C_DecryptFinal;
+	CK_VOID_PTR C_DigestInit;
+	CK_VOID_PTR C_Digest;
+	CK_VOID_PTR C_DigestUpdate;
+	CK_VOID_PTR C_DigestKey;
+	CK_VOID_PTR C_DigestFinal;
+	CK_C_SignInit C_SignInit;
+	CK_C_Sign C_Sign;
+	CK_VOID_PTR C_SignUpdate;
+	CK_VOID_PTR C_SignFinal;
+	CK_VOID_PTR C_SignRecoverInit;
+	CK_VOID_PTR C_SignRecover;
+	CK_VOID_PTR C_VerifyInit;
+	CK_VOID_PTR C_Verify;
+	CK_VOID_PTR C_VerifyUpdate;
+	CK_VOID_PTR C_VerifyFinal;
+	CK_VOID_PTR C_VerifyRecoverInit;
+	CK_VOID_PTR C_VerifyRecover;
+	CK_VOID_PTR C_DigestEncryptUpdate;
+	CK_VOID_PTR C_DecryptDigestUpdate;
+	CK_VOID_PTR C_SignEncryptUpdate;
+	CK_VOID_PTR C_DecryptVerifyUpdate;
+	CK_VOID_PTR C_GenerateKey;
+	CK_VOID_PTR C_GenerateKeyPair;
+	CK_VOID_PTR C_WrapKey;
+	CK_VOID_PTR C_UnwrapKey;
+	CK_VOID_PTR C_DeriveKey;
+	CK_VOID_PTR C_SeedRandom;
+	CK_VOID_PTR C_GenerateRandom;
+	CK_VOID_PTR C_GetFunctionStatus;
+	CK_VOID_PTR C_CancelFunction;
+	CK_VOID_PTR C_WaitForSlotEvent;
+ } CK_FUNCTION_LIST;
+
+#define CKR_OK  0x00000000UL
+
+/* valid values that are used for CK_MECHANISM_TYPE */
+#define CKM_RSA_X_509            0x00000003UL
+#define CKM_SHA1_RSA_PKCS        0x00000006UL
+#define CKM_SHA256_RSA_PKCS      0x00000040UL
+#define CKM_SHA384_RSA_PKCS      0x00000041UL
+#define CKM_SHA512_RSA_PKCS      0x00000042UL
+#define CKM_ECDSA_SHA1           0x00001042UL
+#define CKM_ECDSA_SHA256         0x00001044UL
+#define CKM_ECDSA_SHA384         0x00001045UL
+#define CKM_ECDSA_SHA512         0x00001046UL
+#define CKM_SHA256_RSA_PKCS_PSS  0x00000043UL
+#define CKM_SHA384_RSA_PKCS_PSS  0x00000044UL
+#define CKM_SHA512_RSA_PKCS_PSS  0x00000045UL
+#define CKM_EDDSA                0x00001057UL
+
+#define CK_INVALID_HANDLE				0UL
+
+#define CKF_TOKEN_PRESENT	0x00000001UL
+
+#define CK_TRUE   1
+#define CK_FALSE  0
+
+#define CKU_USER  1UL
+
+#define CKA_CLASS  0x00000000UL
+#define CKA_LABEL  0x00000003UL
+#define CKA_ID     0x00000102UL
+
+#define CKO_PRIVATE_KEY  0x00000003UL
+
+#define CKF_SERIAL_SESSION  0x00000004UL
+
+#endif  /* _HAPROXY_PKCS11_SPEC_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/include/haproxy/pkcs11-token-t.h b/include/haproxy/pkcs11-token-t.h
new file mode 100644
index 000000000..7727b8af1
--- /dev/null
+++ b/include/haproxy/pkcs11-token-t.h
@@ -0,0 +1,91 @@
+/*
+ * include/haproxy/pkcs11-t.h
+ * PKCS#11 internal structures for token management.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_TOKEN_T_H
+#define _HAPROXY_PKCS11_TOKEN_T_H
+
+#include <haproxy/list.h>
+#include <haproxy/pkcs11-spec.h>
+
+struct pkcs11_module {
+	/* the list of modules */
+	struct mt_list list;
+	/* the reference count of this module in pkcs11_token */
+	int ref_count;
+	/* the path to this module */
+	char *module_path;
+	/* the loaded module through dlopen */
+	void *module;
+	/* the module functions */
+	CK_FUNCTION_LIST_PTR functions;
+};
+
+struct pkcs11_session {
+	/* the list that this session belongs to */
+	struct mt_list list;
+	/* the session for this token */
+	CK_SESSION_HANDLE session;
+	/* the handle for the key */
+	CK_OBJECT_HANDLE key;
+};
+
+struct pkcs11_token {
+	/* the reference count for this token */
+	int ref_count;
+	/* the module that this token is loaded through */
+	struct pkcs11_module *module;
+	/* the slot ID of this token */
+	CK_SLOT_ID slot_id;
+	/* a list of sessions that are unused */
+	struct mt_list sessions;
+	/* the id of the token */
+	char *id;
+	/* the length of id */
+	int id_len;
+	/* the object of the token */
+	char *object;
+	/* the length of object */
+	int object_len;
+};
+
+enum job_type {
+	job_type_sign,
+	job_type_decrypt,
+};
+
+struct pkcs11_job {
+	/* the list of jobs */
+	struct list list;
+	/* the type of job this is */
+	enum job_type type;
+	/* the SSL_SIGN_* algorithm for sign jobs */
+	int signature_algorithm;
+	/* the notify for completion of the job */
+	struct pkcs11_notify *notify;
+	/* the token to perform the operation on */
+	struct pkcs11_token *token;
+	/* the length of in */
+	int in_len;
+	/* the input data for the job */
+	uint8_t in[];
+};
+
+#endif  /* _HAPROXY_PKCS11_TOKEN_T_H */
diff --git a/include/haproxy/pkcs11-token.h b/include/haproxy/pkcs11-token.h
new file mode 100644
index 000000000..cb5bf49dc
--- /dev/null
+++ b/include/haproxy/pkcs11-token.h
@@ -0,0 +1,66 @@
+/*
+ * include/haproxy/pkcs11-token.h
+ * PKCS#11 token communication.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_TOKEN_H
+#define _HAPROXY_PKCS11_TOKEN_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct pkcs11_token;
+struct pkcs11_notify;
+struct pkcs11_uri;
+
+/* initialise the PKCS#11 token communication library with the given number of
+ * worker threads to perform synchronous actions on
+ */
+int pkcs11_token_init(int threads);
+
+/* shutdown the worker threads */
+void pkcs11_token_deinit(void);
+
+/* attempt to communicate with a given PKCS#11 URI and obtain a reference to
+ * it which can be re-used, returns NULL on failure.  this method is blocking
+ */
+struct pkcs11_token *pkcs11_token_load(struct pkcs11_uri *uri);
+
+/* loads the SSL preferences for a given token into the prefs array, this must
+ * be able to contain all of the SSL prefs available (i.e. max_prefs).  this
+ * method is blocking.
+ */
+int pkcs11_token_get_prefs(struct pkcs11_token *token,
+                           uint16_t *prefs, int *num_prefs, int max_prefs);
+
+/* start an asynchronous signing action and complete this with a pkcs11_notify */
+int pkcs11_token_sign(struct pkcs11_token *token, struct pkcs11_notify *notify,
+                      int signature_algorithm, const uint8_t *in, size_t in_len);
+
+/* start an asynchronous decryption action and complete this with a pkcs11_notify */
+int pkcs11_token_decrypt(struct pkcs11_token *token, struct pkcs11_notify *notify,
+                         const uint8_t *in, size_t in_len);
+
+/* release a reference on a given token */
+void pkcs11_token_free(struct pkcs11_token *token);
+
+/* obtain another reference to a given token */
+struct pkcs11_token *pkcs11_token_dup(struct pkcs11_token *token);
+
+#endif /* _HAPROXY_PKCS11_TOKEN_H */
diff --git a/include/haproxy/pkcs11-uri-t.h b/include/haproxy/pkcs11-uri-t.h
new file mode 100644
index 000000000..28cb45e89
--- /dev/null
+++ b/include/haproxy/pkcs11-uri-t.h
@@ -0,0 +1,74 @@
+/*
+ * include/haproxy/pkcs11-uri-t.h
+ * PKCS#11 parts parsed from a URI used to find a private signing key.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_URI_T_H
+#define _HAPROXY_PKCS11_URI_T_H
+
+struct pkcs11_uri {
+    /* the PKCS#11 library to load */
+    char *module;
+    /* the source of a PIN, likely a file path */
+    char *pin_source;
+    /* the length of pin_source */
+    int pin_source_len;
+    /* the value of a PIN */
+    char *pin_value;
+    /* the length of pin_value */
+    int pin_value_len;
+    /* the slot ID for the token */
+    char *slot_id;
+    /* the length of slot_id */
+    int slot_id_len;
+    /* the slot manufacturer */
+    char *slot_manufacturer;
+    /* the length of slot_manufacturer */
+    int slot_manufacturer_len;
+    /* the slot description */
+    char *slot_description;
+    /* the length of slot_description */
+    int slot_description_len;
+    /* the token name */
+    char *token;
+    /* the length of token */
+    int token_len;
+    /* the token manufacturer */
+    char *manufacturer;
+    /* the length of manufacturer */
+    int manufacturer_len;
+    /* the token model */
+    char *model;
+    /* the length of model */
+    int model_len;
+    /* the serial of the token */
+    char *serial;
+    /* the length of serial */
+    int serial_len;
+    /* the id of the token */
+    char *id;
+    /* the length of id */
+    int id_len;
+    /* the object of the token */
+    char *object;
+    /* the length of object */
+    int object_len;
+};
+
+#endif /* _HAPROXY_PKCS11_URI_T_H */
diff --git a/include/haproxy/pkcs11-uri.h b/include/haproxy/pkcs11-uri.h
new file mode 100644
index 000000000..f87dfb647
--- /dev/null
+++ b/include/haproxy/pkcs11-uri.h
@@ -0,0 +1,36 @@
+/*
+ * include/haproxy/pkcs11-uri.h
+ * PKCS#11 URI parsing per RFC7512.
+ *
+ * Copyright (C) 2025 Chris Staite - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_URI_H
+#define _HAPROXY_PKCS11_URI_H
+
+#include <stddef.h>
+
+struct pkcs11_uri;
+
+/* parse a UTF-8 string into a struct pkcs11_uri, returns NULL on failure */
+struct pkcs11_uri *pkcs11_uri_parse(const unsigned char *utf8_uri, int len,
+                                    const char *default_module);
+
+/* free a pkcs11_uri structure */
+void pkcs11_uri_free(struct pkcs11_uri *uri);
+
+#endif /* _HAPROXY_PKCS11_URI_H */
diff --git a/include/haproxy/pkcs11.h b/include/haproxy/pkcs11.h
new file mode 100644
index 000000000..9c92e8b8c
--- /dev/null
+++ b/include/haproxy/pkcs11.h
@@ -0,0 +1,73 @@
+/*
+ * include/haproxy/pkcs11.h
+ * PKCS11 module forwarding.
+ *
+ * Copyright (C) 2025 Menlo Security, Inc. - christopher.staite@menlosecurity.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _HAPROXY_PKCS11_H
+#define _HAPROXY_PKCS11_H
+#ifdef USE_OPENSSL
+
+/* the information required to add a private key to an SSL_CTX, defined even
+ * without support to allow a NULL ptr
+ */
+struct pkcs11_data;
+
+#ifdef USE_PKCS11
+
+#include <haproxy/openssl-compat.h>
+#include <haproxy/task-t.h>
+
+/* attempt to parse a BIO as a PEM which contains a PKCS#11 PROVIDER URI and
+ * then return an pkcs11_data which points to the given provider.
+ * If there is no valid PKCS#11 provider in the BIO, or any error occurs then
+ * NULL is returned.
+ */
+struct pkcs11_data *pkcs11_parse_pem(BIO *pem);
+
+/* set up PKCS11 on the SSL_CTX, the key_method must not be NULL */
+int pkcs11_set_private_key(SSL_CTX *ctx, struct pkcs11_data *key_method);
+
+/* check that a PKCS11 key matches a given certificate */
+int pkcs11_check_private_key(X509 *cert, struct pkcs11_data *key_method);
+
+/* schedule a wake up of the tasklet when the current PKCS#11 operation is
+ * complete for the given SSL connection.
+ */
+void pkcs11_schedule_wakeup(SSL *ssl, struct wait_event *wait_event);
+
+/* duplicate an pkcs11_data provided by parse_pkcs11_pem, this may
+ * be through reference counting or memory allocation.
+ */
+struct pkcs11_data *pkcs11_dup(struct pkcs11_data *key_method);
+
+/* free an pkcs11_data provided by parse_pkcs11_pem, this may simply
+ * be decrementing a reference count.
+ */
+void pkcs11_free(struct pkcs11_data *key_method);
+
+#endif   /* USE_PKCS11 */
+#endif  /* USE_OPENSSL */
+#endif  /* _HAPROXY_PKCS11_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
diff --git a/include/haproxy/ssl_ckch-t.h b/include/haproxy/ssl_ckch-t.h
index 83ac3a30c..0f8c5f044 100644
--- a/include/haproxy/ssl_ckch-t.h
+++ b/include/haproxy/ssl_ckch-t.h
@@ -49,6 +49,7 @@
 struct ckch_data {
 	X509 *cert;
 	EVP_PKEY *key;
+	struct pkcs11_data *key_method;
 	STACK_OF(X509) *chain;
 	HASSL_DH *dh;
 	struct buffer *sctl;
diff --git a/src/pkcs11-notify.c b/src/pkcs11-notify.c
new file mode 100644
index 000000000..e2420bd56
--- /dev/null
+++ b/src/pkcs11-notify.c
@@ -0,0 +1,125 @@
+
+#include <haproxy/atomic.h>
+#include <haproxy/pkcs11-notify.h>
+#include <haproxy/task.h>
+#include <haproxy/tools-t.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct pkcs11_notify {
+	/* whether an action is currently pending, if this is 0 but pending_out is
+	 * not NULL then the action is waiting to be collected, if this is 1 then
+	 * pending_out will be non-NULL and the action is queued or being processed,
+	 * if this is 2 then the action was queued but was freed before it
+	 * completed.
+	 */
+	int pending;
+	/* the wait_event to notify when a pending operation is complete */
+	struct wait_event *wait_event;
+	/* if set, the buffer ready to output the data into */
+	uint8_t *pending_out;
+	/* the number of bytes in pending_out */
+	size_t pending_len;
+};
+
+struct pkcs11_notify *pkcs11_notify_new(void)
+{
+	return calloc(1, sizeof(struct pkcs11_notify));
+}
+
+int pkcs11_notify_alloc(struct pkcs11_notify *notify, size_t max_out)
+{
+	int ret = 0;
+	int old = 0;
+	uint8_t *output_buffer = calloc(max_out, sizeof(uint8_t));
+
+	if (output_buffer == NULL)
+		goto out;
+	if (notify->pending_out != NULL || !HA_ATOMIC_CAS(&notify->pending, &old, 1)) {
+		free(output_buffer);
+		output_buffer = NULL;
+		goto out;
+	}
+	notify->pending_out = output_buffer;
+	notify->pending_len = max_out;
+	ret = 1;
+
+out:
+	return ret;
+}
+
+uint8_t *pkcs11_notify_buffer(struct pkcs11_notify *notify)
+{
+	return notify->pending_out;
+}
+
+size_t pkcs11_notify_size(struct pkcs11_notify *notify)
+{
+	return notify->pending_len;
+}
+
+void pkcs11_notify_complete(struct pkcs11_notify *notify, size_t len)
+{
+	int pending = HA_ATOMIC_SUB_FETCH(&notify->pending, 1);
+	int tid;
+
+	if (pending == 1) {
+		/* complete occurred after pkcs11_notify_free was called */
+		free(notify->pending_out);
+		free(notify);
+	} else {
+		notify->pending_len = len;
+		if (notify->wait_event) {
+			tid = notify->wait_event->tasklet->tid;
+			if (tid < 0) {
+				/* cannot wake on the current tid, so place on 0 */
+				tid = 0;
+			}
+			tasklet_wakeup_on(notify->wait_event->tasklet, tid);
+		}
+	}
+}
+
+int pkcs11_notify_clear(struct pkcs11_notify *notify,
+						uint8_t *out, size_t *out_len, size_t max_len)
+{
+	int ret = 0;
+
+	if (HA_ATOMIC_LOAD(&notify->pending) == 1)
+		ret = -1;
+	else if (notify->pending_out) {
+		*out_len = notify->pending_len;
+		if (*out_len > max_len)
+			*out_len = max_len;
+		memcpy(out, notify->pending_out, *out_len);
+		free(notify->pending_out);
+		notify->pending_out = NULL;
+		notify->wait_event = NULL;
+		ret = 1;
+	}
+	return ret;
+}
+
+void pkcs11_notify(struct pkcs11_notify *notify, struct wait_event *wait_event)
+{
+	notify->wait_event = wait_event;
+	if (HA_ATOMIC_LOAD(&notify->pending) != 1)
+		tasklet_wakeup(wait_event->tasklet);
+}
+
+void pkcs11_notify_free(struct pkcs11_notify *notify)
+{
+	int pending = HA_ATOMIC_ADD_FETCH(&notify->pending, 1);
+
+	if (pending != 2) {
+		notify->wait_event = NULL;
+		if (notify->pending_out) {
+			free(notify->pending_out);
+			notify->pending_out = NULL;
+		}
+		free(notify);
+	}
+}
diff --git a/src/pkcs11-token.c b/src/pkcs11-token.c
new file mode 100644
index 000000000..6d19a85f9
--- /dev/null
+++ b/src/pkcs11-token.c
@@ -0,0 +1,689 @@
+
+#include <haproxy/errors.h>
+#include <haproxy/list.h>
+#include <haproxy/pkcs11-notify.h>
+#include <haproxy/pkcs11-spec.h>
+#include <haproxy/pkcs11-token.h>
+#include <haproxy/pkcs11-token-t.h>
+#include <haproxy/pkcs11-uri-t.h>
+#include <haproxy/tools.h>
+#include <openssl/ssl.h>
+
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+static struct {
+	/* the number of running worker threads */
+	int thread_count;
+	/* the handles to the worker threads */
+	pthread_t *thread_handles;
+	/* the job queue which is processed by the worker threads */
+	struct list queue;
+	/* a mutex for the queue, we don't use mt_list because the queue is often
+	 * empty and we don't want to spin on NULL checks
+	 */
+	pthread_mutex_t mutex;
+	/* a condition variable to wake the threads when the queue is not empty */
+	pthread_cond_t condition;
+	/* a list of loaded pkcs11_modules */
+	struct mt_list modules;
+} global_pkcs11_token = {
+	.thread_count = 0,
+	.thread_handles = NULL,
+	.queue = LIST_HEAD_INIT(global_pkcs11_token.queue),
+	.mutex = PTHREAD_MUTEX_INITIALIZER,
+	.condition = PTHREAD_COND_INITIALIZER,
+	.modules = MT_LIST_HEAD_INIT(global_pkcs11_token.modules),
+};
+
+static int find_key(struct pkcs11_module *module,
+					CK_SESSION_HANDLE session,
+					char *id, int id_len,
+					char *object, int object_len,
+					CK_OBJECT_HANDLE *key)
+{
+	CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
+	CK_ATTRIBUTE attributes[3] = {
+		{CKA_CLASS, &key_class, sizeof(key_class)},
+	};
+	CK_ULONG attribute_count = 1;
+	CK_ULONG found_keys = 0;
+
+	if (id) {
+		attributes[attribute_count].type = CKA_ID;
+		attributes[attribute_count].pValue = id;
+		attributes[attribute_count].ulValueLen = id_len;
+		++attribute_count;
+	}
+	if (object) {
+		attributes[attribute_count].type = CKA_LABEL;
+		attributes[attribute_count].pValue = object;
+		attributes[attribute_count].ulValueLen = object_len;
+		++attribute_count;
+	}
+	if (module->functions->C_FindObjectsInit(session, attributes, attribute_count) != CKR_OK)
+		goto out;
+	if (module->functions->C_FindObjects(session, key, 1, &found_keys) != CKR_OK)
+		found_keys = 0;
+	(void) module->functions->C_FindObjectsFinal(session);
+out:
+	return found_keys;
+}
+
+static struct pkcs11_session *get_session(struct pkcs11_token *token)
+{
+	struct pkcs11_session *p11_session;
+	CK_SESSION_HANDLE session = CK_INVALID_HANDLE;
+	CK_OBJECT_HANDLE key;
+
+	p11_session = MT_LIST_POP(&token->sessions, struct pkcs11_session*, list);
+	if (p11_session != NULL)
+		goto out;
+	/* try to create a new session */
+	if (token->module->functions->C_OpenSession(token->slot_id, CKF_SERIAL_SESSION, NULL, NULL, &session) != CKR_OK)
+		goto out;
+	if (!find_key(token->module, session, token->id, token->id_len, token->object, token->object_len, &key))
+		goto out;
+	p11_session = calloc(1, sizeof(struct pkcs11_session));
+	if (p11_session == NULL)
+		goto out;
+	p11_session->session = session;
+	session = CK_INVALID_HANDLE;
+	p11_session->key = key;
+
+out:
+	if (session != CK_INVALID_HANDLE)
+		token->module->functions->C_CloseSession(session);
+	/* ensure that the session is configured to be added back */
+	if (p11_session)
+		MT_LIST_INIT(&p11_session->list);
+	return p11_session;
+}
+
+static void release_session(struct pkcs11_token *token, struct pkcs11_session *session)
+{
+	MT_LIST_APPEND(&token->sessions, &session->list);
+}
+
+static int get_mechanism(int signature_algorithm, CK_MECHANISM_TYPE *mechanism)
+{
+	int ret = 1;
+
+	switch (signature_algorithm) {
+	case SSL_SIGN_RSA_PKCS1_SHA1:
+		*mechanism = CKM_SHA1_RSA_PKCS;
+		break;
+	case SSL_SIGN_RSA_PKCS1_SHA256:
+		*mechanism = CKM_SHA256_RSA_PKCS;
+		break;
+	case SSL_SIGN_RSA_PKCS1_SHA384:
+		*mechanism = CKM_SHA384_RSA_PKCS;
+		break;
+	case SSL_SIGN_RSA_PKCS1_SHA512:
+		*mechanism = CKM_SHA512_RSA_PKCS;
+		break;
+	case SSL_SIGN_ECDSA_SHA1:
+		*mechanism = CKM_ECDSA_SHA1;
+		break;
+	case SSL_SIGN_ECDSA_SECP256R1_SHA256:
+		*mechanism = CKM_ECDSA_SHA256;
+		break;
+	case SSL_SIGN_ECDSA_SECP384R1_SHA384:
+		*mechanism = CKM_ECDSA_SHA384;
+		break;
+	case SSL_SIGN_ECDSA_SECP521R1_SHA512:
+		*mechanism = CKM_ECDSA_SHA512;
+		break;
+	case CKM_SHA256_RSA_PKCS_PSS:
+		*mechanism = SSL_SIGN_RSA_PSS_RSAE_SHA256;
+		break;
+	case CKM_SHA384_RSA_PKCS_PSS:
+		*mechanism = SSL_SIGN_RSA_PSS_RSAE_SHA384;
+		break;
+	case CKM_SHA512_RSA_PKCS_PSS:
+		*mechanism = SSL_SIGN_RSA_PSS_RSAE_SHA512;
+		break;
+	case CKM_EDDSA:
+		*mechanism = SSL_SIGN_ED25519;
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+static int execute_sign(CK_FUNCTION_LIST_PTR functions,
+						CK_SESSION_HANDLE session,
+						CK_OBJECT_HANDLE key,
+						CK_MECHANISM_TYPE mechanism_type,
+						uint8_t *in, int in_len,
+						uint8_t *out, int max_out)
+{
+	CK_ULONG out_len = 0;
+	CK_MECHANISM mechanism = {
+		.mechanism = mechanism_type,
+		.pParameter = NULL,
+		.ulParameterLen = 0,
+	};
+
+	if (functions->C_SignInit(session, &mechanism, key) != CKR_OK)
+		goto out;
+	out_len = max_out;
+	if (functions->C_Sign(session, in, in_len, out, &out_len) != CKR_OK)
+		out_len = 0;
+out:
+	return out_len;
+}
+
+static int execute_decrypt(CK_FUNCTION_LIST_PTR functions,
+						   CK_SESSION_HANDLE session,
+						   CK_OBJECT_HANDLE key,
+						   uint8_t *in, int in_len,
+						   uint8_t *out, int max_out)
+{
+	CK_ULONG out_len = 0;
+	CK_MECHANISM mechanism = {
+		.mechanism = CKM_RSA_X_509,
+		.pParameter = NULL,
+		.ulParameterLen = 0,
+	};
+
+	if (functions->C_DecryptInit(session, &mechanism, key) != CKR_OK)
+		goto out;
+	out_len = max_out;
+	if (functions->C_Decrypt(session, in, in_len, out, &out_len) != CKR_OK)
+		out_len = 0;
+out:
+	return out_len;
+}
+
+static void execute_job(struct pkcs11_job *job)
+{
+	int max_out = pkcs11_notify_size(job->notify);
+	uint8_t *out = pkcs11_notify_buffer(job->notify);
+	int out_len = 0;
+	struct pkcs11_session *session = NULL;
+	CK_MECHANISM_TYPE mechanism = 0;
+
+	if (job->type == job_type_sign &&
+			!get_mechanism(job->signature_algorithm, &mechanism)) {
+		goto out;
+	}
+	session = get_session(job->token);
+	if (session == NULL) {
+		/* there is at least one session for this token, so re-queue */
+		LIST_INIT(&job->list);
+		pthread_mutex_lock(&global_pkcs11_token.mutex);
+		LIST_APPEND(&global_pkcs11_token.queue, &job->list);
+		pthread_mutex_unlock(&global_pkcs11_token.mutex);
+		job = NULL;
+		goto out;
+	}
+	switch (job->type) {
+	case job_type_sign:
+		out_len = execute_sign(job->token->module->functions, session->session, session->key, mechanism, job->in, job->in_len, out, max_out);
+		break;
+	case job_type_decrypt:
+		out_len = execute_decrypt(job->token->module->functions, session->session, session->key, job->in, job->in_len, out, max_out);
+		break;
+	}
+
+out:
+	if (session)
+		release_session(job->token, session);
+	if (job) {
+		pkcs11_notify_complete(job->notify, out_len);
+		pkcs11_token_free(job->token);
+		free(job);
+	}
+}
+
+static void submit_job(struct pkcs11_job *job)
+{
+	pthread_mutex_lock(&global_pkcs11_token.mutex);
+	LIST_APPEND(&global_pkcs11_token.queue, &job->list);
+	pthread_cond_signal(&global_pkcs11_token.condition);
+	pthread_mutex_unlock(&global_pkcs11_token.mutex);
+}
+
+static int submit_task(struct pkcs11_token *token, struct pkcs11_notify *notify,
+                       int signature_algorithm, const uint8_t *in, size_t in_len, enum job_type type)
+{
+	int ret = 0;
+	struct pkcs11_job *job = calloc(1, sizeof(struct pkcs11_job) + in_len);
+
+	if (job == NULL) {
+		pkcs11_notify_complete(notify, 0);
+		goto out;
+	}
+	job->type = type;
+	job->token = pkcs11_token_dup(token);
+	LIST_INIT(&job->list);
+	job->in_len = in_len;
+	memcpy(job->in, in, in_len);
+	job->notify = notify;
+	submit_job(job);
+	ret = 1;
+out:
+	return ret;
+}
+
+static void *token_worker_thread(void *arg)
+{
+	struct pkcs11_job *job;
+
+	pthread_mutex_lock(&global_pkcs11_token.mutex);
+	while (global_pkcs11_token.thread_count) {
+		pthread_cond_wait(&global_pkcs11_token.condition, &global_pkcs11_token.mutex);
+		while (!LIST_ISEMPTY(&global_pkcs11_token.queue)) {
+			job = LIST_NEXT(&global_pkcs11_token.queue, struct pkcs11_job *, list);
+			if (job) {
+				LIST_DELETE(&job->list);
+				pthread_mutex_unlock(&global_pkcs11_token.mutex);
+				execute_job(job);
+				pthread_mutex_lock(&global_pkcs11_token.mutex);
+			}
+		}
+	}
+	return NULL;
+}
+
+int pkcs11_token_init(int threads)
+{
+	int ret = ERR_NONE;
+	int i;
+
+	global_pkcs11_token.thread_handles = calloc(threads, sizeof(pthread_t));
+	if (global_pkcs11_token.thread_handles == NULL) {
+		ha_alert("pkcs11 : unable to allocate %d worker threads.\n", threads);
+		ret |= ERR_ALERT | ERR_FATAL;
+		goto out;
+	}
+	global_pkcs11_token.thread_count = threads;
+	for (i = 0; i < threads; ++i) {
+		if (pthread_create(&global_pkcs11_token.thread_handles[i], NULL,
+						   token_worker_thread, NULL) != 0) {
+			ha_alert("pkcs11 : unable to start worker thread %d of %d.\n",
+					 i, threads);
+			ret |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+
+out:
+	return ret;
+}
+
+void pkcs11_token_deinit(void)
+{
+	int i;
+	int threads = global_pkcs11_token.thread_count;
+
+	pthread_mutex_lock(&global_pkcs11_token.mutex);
+	global_pkcs11_token.thread_count = 0;
+	pthread_cond_broadcast(&global_pkcs11_token.condition);
+	pthread_mutex_unlock(&global_pkcs11_token.mutex);
+	for (i = 0; i < threads; ++i)
+		pthread_join(global_pkcs11_token.thread_handles[i], NULL);
+	if (global_pkcs11_token.thread_handles)
+		free(global_pkcs11_token.thread_handles);
+	global_pkcs11_token.thread_handles = NULL;
+}
+
+static int pkcs11_module_init(struct pkcs11_module *module)
+{
+	int ret = 0;
+	CK_C_GetFunctionList get_function_list = dlsym(module->module, "C_GetFunctionList");
+
+	if (get_function_list == NULL)
+		goto out;
+	if (get_function_list(&module->functions) != CKR_OK)
+		goto out;
+	if (module->functions->C_Initialize(NULL) == CKR_OK)
+		ret = 1;
+
+out:
+	return ret;
+}
+
+static struct pkcs11_module *pkcs11_module_load(const char *module_path)
+{
+	struct pkcs11_module *module;
+	struct mt_list back;
+
+	MT_LIST_FOR_EACH_ENTRY_LOCKED(module, &global_pkcs11_token.modules, list, back) {
+		if (strcmp(module->module_path, module_path) == 0) {
+			HA_ATOMIC_INC(&module->ref_count);
+			goto err;
+		}
+	}
+
+	module = calloc(1, sizeof(struct pkcs11_module));
+	if (module == NULL)
+		goto err;
+	module->ref_count = 1;
+	module->module_path = strdup(module_path);
+	if (module->module_path == NULL)
+		goto err;
+	module->module = dlopen(module_path, RTLD_LOCAL | RTLD_NOW);
+	if (module->module == NULL)
+		goto err;
+	if (!pkcs11_module_init(module))
+		goto err;
+	MT_LIST_INIT(&module->list);
+	MT_LIST_APPEND(&global_pkcs11_token.modules, &module->list);
+
+	return module;
+
+err:
+	if (module) {
+		if (module->module_path)
+			free(module->module_path);
+		if (module->module)
+			dlclose(module->module);
+		free(module);
+	}
+	return NULL;
+}
+
+static void pkcs11_module_free(struct pkcs11_module *module)
+{
+	if (HA_ATOMIC_SUB_FETCH(&module->ref_count, 1) == 0) {
+		MT_LIST_DELETE(&module->list);
+		free(module->module_path);
+		module->functions->C_Finalize(NULL);
+		dlclose(module->module);
+		free(module);
+	}
+}
+
+/* compares two UTF-8 strings, str2 may be padded with spaces */
+static int utf8cmp(const char *str1, int str1len, CK_UTF8CHAR *str2, int str2len)
+{
+	const char *str1ptr = str1;
+	const char *str1end = str1 + str1len;
+	const char *str2ptr = (const char *) str2;
+	const char *str2end = str2ptr + str2len;
+	unsigned int ret1, c1, ret2, c2;
+	int ret = 0;
+
+	while (ret == 0 && str2ptr < str2end) {
+		if (str1ptr < str1end) {
+			ret1 = utf8_next(str1ptr, (str1end - str1ptr), &c1);
+			str1ptr += utf8_return_length(ret1);
+			if (utf8_return_code(ret1) != UTF8_CODE_OK)
+				ret = 1;
+		} else
+			c1 = ' ';
+		ret2 = utf8_next(str2ptr, (str2end - str2ptr), &c2);
+		str2ptr += utf8_return_length(ret2);
+		if (utf8_return_code(ret2) != UTF8_CODE_OK || c1 != c2)
+			ret = 1;
+	}
+	return ret;
+}
+
+/* compares two byte strings, str2 may be padded with spaces */
+static int bytescmp(const char *str1, int str1len, CK_CHAR *str2, int str2len)
+{
+	int ret = 1;
+	CK_CHAR *str2pad;
+
+	if (str2len < str1len || memcmp(str1, str2, str1len) != 0)
+		ret = 0;
+	else {
+		/* padding can be spaces */
+		str2pad = str2 + str1len;
+		while (str2pad < str2 + str2len) {
+			if (*str2pad++ != ' ') {
+				ret = 0;
+			}
+		}
+	}
+	return ret;
+}
+
+/* check whether a slot matches the description in the given URI */
+static int slot_matches(struct pkcs11_uri *uri, CK_SLOT_INFO *slot_info)
+{
+	int ret = 1;
+
+	if (uri->slot_manufacturer && utf8cmp(uri->slot_manufacturer, uri->slot_manufacturer_len, slot_info->manufacturerID, 32))
+		ret = 0;
+	else if (uri->slot_description && utf8cmp(uri->slot_description, uri->slot_description_len, slot_info->slotDescription, 64))
+		ret = 0;
+	return ret;
+}
+
+/* check whether a token matches the description in the given URI */
+static int token_matches(struct pkcs11_module *module,
+						 struct pkcs11_uri *uri,
+						 CK_SLOT_ID slot_id)
+{
+	CK_TOKEN_INFO token_info;
+	int ret = 0;
+
+	if (module->functions->C_GetTokenInfo(slot_id, &token_info) != CKR_OK)
+		goto out;
+	ret = 1;
+	if (uri->serial && bytescmp(uri->serial, uri->serial_len, token_info.serialNumber, 16))
+		ret = 0;
+	else if (uri->model && utf8cmp(uri->model, uri->model_len, token_info.model, 16))
+		ret = 0;
+	else if (uri->manufacturer && utf8cmp(uri->manufacturer, uri->manufacturer_len, token_info.manufacturerID, 32))
+		ret = 0;
+	else if (uri->token && utf8cmp(uri->token, uri->token_len, token_info.label, 32))
+		ret = 0;
+out:
+	return ret;
+}
+
+struct pkcs11_token *pkcs11_token_load(struct pkcs11_uri *uri)
+{
+	struct pkcs11_token *token = NULL;
+	struct pkcs11_module *module = NULL;
+	CK_SLOT_ID slot_id;
+	CK_SLOT_INFO slot_info;
+	CK_SLOT_ID *slot_list = NULL;
+	CK_ULONG i, slot_count = 0;
+	CK_SESSION_HANDLE session = CK_INVALID_HANDLE;
+	CK_OBJECT_HANDLE key;
+	struct pkcs11_session *p11_session = NULL;
+	char *id = NULL, *object = NULL;
+
+	if (uri->module == NULL)
+		goto out;
+	module = pkcs11_module_load(uri->module);
+	if (module == NULL)
+		goto out;
+
+	/* easy lookup by the slot ID first if it's available. */
+	if (uri->slot_id) {
+		slot_id = atoi(uri->slot_id);
+		if (module->functions->C_GetSlotInfo(slot_id, &slot_info) == CKR_OK) {
+			if (!(slot_info.flags & CKF_TOKEN_PRESENT) ||
+					!token_matches(module, uri, slot_id)) {
+				goto out;
+			}
+			goto found_slot;
+		}
+	}
+
+	/* slot ID not specified, search for the token instead. */
+	if (module->functions->C_GetSlotList(CK_TRUE, NULL, &slot_count) != CKR_OK ||
+			slot_count <= 0) {
+		goto out;
+	}
+	slot_list = calloc(slot_count, sizeof(CK_SLOT_ID));
+	if (slot_list == NULL)
+		goto out;
+	if (module->functions->C_GetSlotList(CK_TRUE, slot_list, &slot_count) != CKR_OK ||
+			slot_count <= 0)
+		goto out;
+	for (i = 0; i < slot_count; ++i) {
+		if (module->functions->C_GetSlotInfo(slot_list[i], &slot_info) == CKR_OK &&
+				slot_matches(uri, &slot_info) &&
+				token_matches(module, uri, slot_list[i])) {
+			slot_id = slot_list[i];
+			goto found_slot;
+		}
+	}
+
+	/* if we haven't jumped to found_slot then the token could not be found. */
+	goto out;
+
+found_slot:
+	/* now we try to log in and get an appropriate key */
+	if (module->functions->C_OpenSession(slot_id, CKF_SERIAL_SESSION, NULL, NULL, &session) != CKR_OK)
+		goto out;
+	// TODO: Support pin_source
+	if (uri->pin_value != NULL &&
+			module->functions->C_Login(session, CKU_USER, (CK_UTF8CHAR *) uri->pin_value, uri->pin_value_len) != CKR_OK)
+		goto out;
+	if (!find_key(module, session, uri->id, uri->id_len, uri->object, uri->object_len, &key))
+		goto out;
+	if (uri->id) {
+		id = malloc(uri->id_len);
+		if (id == NULL)
+			goto out;
+		memcpy(id, uri->id, uri->id_len);
+	}
+	if (uri->object) {
+		object = malloc(uri->object_len);
+		if (object == NULL)
+			goto out;
+		memcpy(object, uri->object, uri->object_len);
+	}
+	p11_session = calloc(1, sizeof(struct pkcs11_session));
+	if (p11_session == NULL)
+		goto out;
+	token = calloc(1, sizeof(struct pkcs11_token));
+	if (token == NULL) {
+		free(p11_session);
+		goto out;
+	}
+	token->ref_count = 1;
+	token->slot_id = slot_id;
+	token->module = module;
+	module = NULL;
+	MT_LIST_INIT(&token->sessions);
+	MT_LIST_INIT(&p11_session->list);
+	MT_LIST_APPEND(&token->sessions, &p11_session->list);
+	p11_session->session = session;
+	session = CK_INVALID_HANDLE;
+	p11_session->key = key;
+	p11_session = NULL;
+	token->id = id;
+	id = NULL;
+	token->id_len = uri->id_len;
+	token->object = object;
+	object = NULL;
+	token->object_len = uri->object_len;
+
+out:
+	if (p11_session)
+		free(p11_session);
+	if (id)
+		free(id);
+	if (object)
+		free(object);
+	if (module && session != CK_INVALID_HANDLE)
+		module->functions->C_CloseSession(session);
+	if (module)
+		pkcs11_module_free(module);
+	if (slot_list)
+		free(slot_list);
+	return token;
+}
+
+int pkcs11_token_get_prefs(struct pkcs11_token *token,
+                           uint16_t *prefs, int *num_prefs, int max_prefs)
+{
+	int ret = 0;
+	CK_MECHANISM_TYPE *types = NULL;
+	CK_ULONG i, count = 0;
+
+	*num_prefs = 0;
+	if (token->module->functions->C_GetMechanismList(token->slot_id, NULL, &count) != CKR_OK || count <= 0)
+		goto out;
+	types = calloc(count, sizeof(CK_MECHANISM_TYPE));
+	if (types == NULL)
+		goto out;
+	if (token->module->functions->C_GetMechanismList(token->slot_id, types, &count) != CKR_OK || count <= 0)
+		goto out;
+	for (i = 0; i < count && *num_prefs < max_prefs; ++i) {
+		switch (types[i]) {
+		case CKM_EDDSA:
+			prefs[(*num_prefs)++] = SSL_SIGN_ED25519;
+			break;
+		case CKM_SHA512_RSA_PKCS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PKCS1_SHA512;
+			break;
+		case CKM_ECDSA_SHA512:
+			prefs[(*num_prefs)++] = SSL_SIGN_ECDSA_SECP521R1_SHA512;
+			break;
+		case CKM_SHA512_RSA_PKCS_PSS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PSS_RSAE_SHA512;
+			break;
+		case CKM_SHA384_RSA_PKCS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PKCS1_SHA384;
+			break;
+		case CKM_ECDSA_SHA384:
+			prefs[(*num_prefs)++] = SSL_SIGN_ECDSA_SECP384R1_SHA384;
+			break;
+		case CKM_SHA384_RSA_PKCS_PSS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PSS_RSAE_SHA384;
+			break;
+		case CKM_SHA256_RSA_PKCS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PKCS1_SHA256;
+			break;
+		case CKM_ECDSA_SHA256:
+			prefs[(*num_prefs)++] = SSL_SIGN_ECDSA_SECP256R1_SHA256;
+			break;
+		case CKM_SHA256_RSA_PKCS_PSS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PSS_RSAE_SHA256;
+			break;
+		case CKM_ECDSA_SHA1:
+			prefs[(*num_prefs)++] = SSL_SIGN_ECDSA_SHA1;
+			break;
+		case CKM_SHA1_RSA_PKCS:
+			prefs[(*num_prefs)++] = SSL_SIGN_RSA_PKCS1_SHA1;
+			break;
+		}
+	}
+	ret = 1;
+
+out:
+	if (types)
+		free(types);
+	return ret;
+}
+
+int pkcs11_token_sign(struct pkcs11_token *token, struct pkcs11_notify *notify,
+                      int signature_algorithm, const uint8_t *in, size_t in_len)
+{
+	return submit_task(token, notify, signature_algorithm, in, in_len, job_type_sign);
+}
+
+int pkcs11_token_decrypt(struct pkcs11_token *token, struct pkcs11_notify *notify,
+                         const uint8_t *in, size_t in_len)
+{
+	return submit_task(token, notify, -1, in, in_len, job_type_decrypt);
+}
+
+void pkcs11_token_free(struct pkcs11_token *token)
+{
+	if (HA_ATOMIC_SUB_FETCH(&token->ref_count, 1) == 0) {
+		pkcs11_module_free(token->module);
+		free(token);
+	}
+}
+
+struct pkcs11_token *pkcs11_token_dup(struct pkcs11_token *token)
+{
+	HA_ATOMIC_INC(&token->ref_count);
+	return token;
+}
diff --git a/src/pkcs11-uri.c b/src/pkcs11-uri.c
new file mode 100644
index 000000000..fcf9842e2
--- /dev/null
+++ b/src/pkcs11-uri.c
@@ -0,0 +1,291 @@
+
+#include <haproxy/pkcs11-uri.h>
+#include <haproxy/pkcs11-uri-t.h>
+#include <haproxy/tools.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+static const char URI[] = "pkcs11:";
+
+/* search for the next instance of a single character c in a UTF-8 string, if
+ * not found before the end returns end, if found returns the pointer after
+ * the needle, returns NULL if an invalid UTF-8 character is encountered.
+ */
+static const char *next_char(const char *ptr, const char *end, char needle)
+{
+	unsigned int ret, c;
+
+	while (ptr < end) {
+		ret = utf8_next(ptr, (end - ptr), &c);
+		ptr += utf8_return_length(ret);
+		if (utf8_return_code(ret) != UTF8_CODE_OK) {
+			ptr = NULL;
+			break;
+		}
+		if (c == needle)
+			break;
+	}
+	return ptr;
+}
+
+/* similar to strdup but for a known length UTF-8 string and also handles
+ * removal of percent encoding.
+ */
+static char *copy_utf8(const char *start, const char *end, int *len)
+{
+	const char *ptr_in = start;
+	char *copy, *ptr_out;
+	unsigned int ret, c;
+	unsigned char char_len;
+
+	ptr_out = copy = calloc(end - start, sizeof(char));
+	if (copy == NULL)
+		goto out;
+
+	while (ptr_in < end) {
+		ret = utf8_next(ptr_in, (end - ptr_in), &c);
+		char_len = utf8_return_length(ret);
+		if (utf8_return_code(ret) != UTF8_CODE_OK)
+			goto err;
+		if (c == '%') {
+			/* decode the percent encoding. */
+			ptr_in += char_len;
+			if ((end - ptr_in) < 2 || !ishex(ptr_in[0]) || !ishex(ptr_in[1]))
+				goto err;
+			*ptr_out = hex2i(*ptr_in++) << 4;
+			*ptr_out++ |= hex2i(*ptr_in++);
+		} else if (c == '+') {
+			/* plus to space */
+			ptr_in += char_len;
+			*ptr_out++ = ' ';
+		} else {
+			/* copy a single UTF-8 character over */
+			memcpy(ptr_out, ptr_in, char_len);
+			ptr_out += char_len;
+			ptr_in += char_len;
+		}
+	}
+
+out:
+	*len = (ptr_out - copy);
+	return copy;
+
+err:
+	free(copy);
+	copy = NULL;
+	ptr_out = NULL;
+	goto out;
+}
+
+/* parse the query part of a token */
+static int parse_query(struct pkcs11_uri *uri, const char *ptr, const char *end)
+{
+	static const char MODULE[] = "module-path";
+	static const char PIN_SOURCE[] = "pin-source";
+	static const char PIN_VALUE[] = "pin-value";
+	int ret = 1;
+	const char *name_end, *value_end, *next;
+
+	while (ptr < end) {
+		name_end = next_char(ptr, end, '=');
+		if (name_end == NULL)
+			break;
+		value_end = next_char(ptr, end, '&');
+		if (value_end == NULL)
+			break;
+		next = value_end;
+		if (value_end != name_end && *(value_end - 1) == '&')
+			--value_end;
+		if (name_end - ptr == sizeof(MODULE) &&
+				memcmp(ptr, MODULE, sizeof(MODULE) - 1) == 0) {
+			uri->module = copy_utf8(name_end, value_end, &uri->model_len);
+			if (uri->module == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(PIN_SOURCE) &&
+				memcmp(ptr, PIN_SOURCE, sizeof(PIN_SOURCE) - 1) == 0) {
+			uri->pin_source = copy_utf8(name_end, value_end, &uri->pin_source_len);
+			if (uri->pin_source == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(PIN_VALUE) &&
+				memcmp(ptr, PIN_VALUE, sizeof(PIN_VALUE) - 1) == 0) {
+			uri->pin_value = copy_utf8(name_end, value_end, &uri->pin_value_len);
+			if (uri->pin_value == NULL) {
+				ret = 0;
+				break;
+			}
+		}
+		ptr = next;
+	}
+	return ret;
+}
+
+/* parse the main path of the token, the end may have a ?, careful to strip it */
+static int parse_path(struct pkcs11_uri *uri, const char *ptr, const char *end)
+{
+	static const char TYPE[] = "type";
+	static const char PRIVATE[] = "private";
+	static const char SLOT_ID[] = "slot-id";
+	static const char TOKEN[] = "token";
+	static const char SERIAL[] = "serial";
+	static const char MANUFACTURER[] = "manufacturer";
+	static const char MODEL[] = "model";
+	static const char SLOT_MANUFACTURER[] = "slot-manufacturer";
+	static const char SLOT_DESCRIPTION[] = "slot-description";
+	static const char OBJECT[] = "object";
+	static const char ID[] = "id";
+	int ret = 0;
+	const char *name_end, *value_end, *next;
+
+	while (ptr < end) {
+		name_end = next_char(ptr, end, '=');
+		if (name_end == NULL)
+			break;
+		value_end = next_char(ptr, end, ';');
+		if (value_end == NULL)
+			break;
+		next = value_end;
+		if (value_end != name_end && *(value_end - 1) == ';')
+			--value_end;
+		if (name_end - ptr == sizeof(TYPE) &&
+				memcmp(ptr, TYPE, sizeof(TYPE) - 1) == 0) {
+			if (value_end - name_end == sizeof(PRIVATE) - 1 &&
+					memcmp(name_end, PRIVATE, sizeof(PRIVATE) - 1) == 0) {
+				/* the only valid PKCS#11 tokens are those with type=private */
+				ret = 1;
+			}
+		} else if (name_end - ptr == sizeof(SLOT_ID) &&
+				memcmp(ptr, SLOT_ID, sizeof(SLOT_ID) - 1) == 0) {
+			uri->slot_id = copy_utf8(name_end, value_end, &uri->slot_id_len);
+			if (uri->slot_id == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(TOKEN) &&
+				memcmp(ptr, TOKEN, sizeof(TOKEN) - 1) == 0) {
+			uri->token = copy_utf8(name_end, value_end, &uri->token_len);
+			if (uri->token == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(SERIAL) &&
+				memcmp(ptr, SERIAL, sizeof(SERIAL) - 1) == 0) {
+			uri->serial = copy_utf8(name_end, value_end, &uri->serial_len);
+			if (uri->serial == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(MANUFACTURER) &&
+				memcmp(ptr, MANUFACTURER, sizeof(MANUFACTURER) - 1) == 0) {
+			uri->manufacturer = copy_utf8(name_end, value_end, &uri->manufacturer_len);
+			if (uri->manufacturer == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(MODEL) &&
+				memcmp(ptr, MODEL, sizeof(MODEL) - 1) == 0) {
+			uri->model = copy_utf8(name_end, value_end, &uri->model_len);
+			if (uri->model == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(SLOT_MANUFACTURER) &&
+				memcmp(ptr, SLOT_MANUFACTURER, sizeof(SLOT_MANUFACTURER) - 1) == 0) {
+			uri->slot_manufacturer = copy_utf8(name_end, value_end, &uri->slot_manufacturer_len);
+			if (uri->slot_manufacturer == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(SLOT_DESCRIPTION) &&
+				memcmp(ptr, SLOT_DESCRIPTION, sizeof(SLOT_DESCRIPTION) - 1) == 0) {
+			uri->slot_description = copy_utf8(name_end, value_end, &uri->slot_description_len);
+			if (uri->slot_description == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(OBJECT) &&
+				memcmp(ptr, OBJECT, sizeof(OBJECT) - 1) == 0) {
+			uri->object = copy_utf8(name_end, value_end, &uri->object_len);
+			if (uri->object == NULL) {
+				ret = 0;
+				break;
+			}
+		} else if (name_end - ptr == sizeof(ID) &&
+				memcmp(ptr, ID, sizeof(ID) - 1) == 0) {
+			uri->id = copy_utf8(name_end, value_end, &uri->id_len);
+			if (uri->id == NULL) {
+				ret = 0;
+				break;
+			}
+		}
+		ptr = next;
+	}
+	return ret;
+}
+
+struct pkcs11_uri *pkcs11_uri_parse(const unsigned char *utf8_uri, int len,
+                                    const char *default_module)
+{
+	const char *ptr = (const char *) utf8_uri;
+	const char *end = ptr + len;
+	const char *query_start;
+	struct pkcs11_uri *uri = NULL;
+
+	/* the URI must start with pkcs11: */
+	if (len < sizeof(URI) - 1 || memcmp(utf8_uri, URI, sizeof(URI) - 1) != 0)
+		goto out;
+	ptr += sizeof(URI) - 1;
+	uri = calloc(1, sizeof(struct pkcs11_uri));
+	if (uri == NULL)
+		goto out;
+	query_start = next_char(ptr, end, '?');
+	if (query_start == NULL) {
+		free(uri);
+		uri = NULL;
+		goto out;
+	}
+	if (query_start != end && parse_query(uri, query_start, end) == 0) {
+		pkcs11_uri_free(uri);
+		uri = NULL;
+		goto out;
+	}
+	if (parse_path(uri, ptr, query_start) == 0) {
+		pkcs11_uri_free(uri);
+		uri = NULL;
+		goto out;
+	}
+	if (uri->module == NULL && default_module != NULL)
+		uri->module = strdup(default_module);
+
+out:
+	return uri;
+}
+
+void pkcs11_uri_free(struct pkcs11_uri *uri)
+{
+	if (uri->module)
+		free(uri->module);
+	if (uri->pin_source)
+		free(uri->pin_source);
+	if (uri->pin_value)
+		free(uri->pin_value);
+	if (uri->token)
+		free(uri->token);
+	if (uri->slot_id)
+		free(uri->slot_id);
+	if (uri->slot_manufacturer)
+		free(uri->slot_manufacturer);
+	if (uri->slot_description)
+		free(uri->slot_description);
+	if (uri->manufacturer)
+		free(uri->manufacturer);
+	if (uri->model)
+		free(uri->model);
+	if (uri->serial)
+		free(uri->serial);
+	free(uri);
+}
diff --git a/src/pkcs11.c b/src/pkcs11.c
new file mode 100644
index 000000000..a7fd0b26c
--- /dev/null
+++ b/src/pkcs11.c
@@ -0,0 +1,383 @@
+
+#include <haproxy/cfgparse.h>
+#include <haproxy/errors.h>
+#include <haproxy/task.h>
+#include <haproxy/tools.h>
+#include <haproxy/proxy-t.h>
+#include <haproxy/pkcs11.h>
+#include <haproxy/pkcs11-notify.h>
+#include <haproxy/pkcs11-token.h>
+#include <haproxy/pkcs11-uri.h>
+#include <openssl/asn1.h>
+
+#include <dlfcn.h>
+#include <stdio.h>
+
+static struct {
+	/* the number of threads that service the synchronous PKCS#11 requests */
+	int worker_threads;
+	/* the ex_data index for SSL_CTX containing the pkcs11_data */
+	int data_index;
+	/* the ex_data index for SSL containing the pkcs11_notify */
+	int ssl_index;
+	/* if configured, the PKCS#11 library to use if not configured in the URI */
+	char *default_library_path;
+} global_pkcs11 = {
+	.worker_threads = 0,
+	.data_index = -1,
+	.ssl_index = -1,
+	.default_library_path = NULL,
+};
+
+static int pkcs11_driver(char **args, int section_type, struct proxy *curpx,
+						 const struct proxy *defpx, const char *file, int line,
+						 char **err)
+{
+	if (*(args[1]) == 0) {
+		memprintf(err, "'%s' expects a library path.", args[0]);
+		return -1;
+	}
+	global_pkcs11.default_library_path = strdup(args[1]);
+	return 0;
+}
+
+static int pkcs11_threads(char **args, int section_type, struct proxy *curpx,
+						  const struct proxy *defpx, const char *file, int line,
+						  char **err)
+{
+	if (*(args[1]) == 0) {
+		memprintf(err, "'%s' expects a positive numeric value.", args[0]);
+		return -1;
+	}
+	global_pkcs11.worker_threads = atoi(args[1]);
+	if (global_pkcs11.worker_threads < 0) {
+		global_pkcs11.worker_threads = 0;
+		memprintf(err, "'%s' expects a positive numeric value, got '%s'.",
+				  args[0], args[1]);
+		return -1;
+	}
+	return 0;
+}
+
+static struct cfg_kw_list pkcs11cfg_kws = {{ }, {
+	{ CFG_GLOBAL, "pkcs11-worker-threads", pkcs11_threads },
+	{ CFG_GLOBAL, "pkcs11-default-driver", pkcs11_driver },
+	{ 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &pkcs11cfg_kws);
+
+struct pkcs11_data {
+	/* a reference to a PKCS#11 private key that can sign and decrypt */
+	struct pkcs11_token *token;
+	/* contains the values of SSL_SIGN supported by this provider in order of
+	 * preference, there are 12 possible values, but not all will be
+	 * populated.  The populated indexes shall start at zero.
+	 */
+	uint16_t prefs[12];
+	/* the number of populated values in prefs. */
+	int num_prefs;
+};
+
+static struct pkcs11_notify *get_notify(SSL *ssl)
+{
+	struct pkcs11_notify *notify = (struct pkcs11_notify *) SSL_get_ex_data(
+		ssl, global_pkcs11.ssl_index);
+	if (notify == NULL) {
+		notify = pkcs11_notify_new();
+		if (notify != NULL && SSL_set_ex_data(ssl, global_pkcs11.ssl_index, notify) <= 0) {
+			pkcs11_notify_free(notify);
+			notify = NULL;
+		}
+	}
+	return notify;
+}
+
+#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL)
+static enum ssl_private_key_result_t pkcs11_sign(
+		SSL *ssl,
+		uint8_t *out, size_t *out_len,
+		size_t max_out,
+		uint16_t signature_algorithm,
+		const uint8_t *in, size_t in_len)
+{
+	struct pkcs11_data *key_method = (struct pkcs11_data *) SSL_CTX_get_ex_data(
+		SSL_get_SSL_CTX(ssl), global_pkcs11.data_index);
+	struct pkcs11_notify *notify = get_notify(ssl);
+	enum ssl_private_key_result_t ret = ssl_private_key_failure;
+
+	if (key_method != NULL && notify != NULL &&
+			pkcs11_notify_alloc(notify, max_out) &&
+			pkcs11_token_sign(key_method->token, notify, signature_algorithm, in, in_len))
+		ret = ssl_private_key_retry;
+	return ret;
+}
+
+static enum ssl_private_key_result_t pkcs11_decrypt(
+		SSL *ssl,
+		uint8_t *out, size_t *out_len, size_t max_out,
+		const uint8_t *in, size_t in_len)
+{
+	struct pkcs11_data *key_method = (struct pkcs11_data *) SSL_CTX_get_ex_data(
+		SSL_get_SSL_CTX(ssl), global_pkcs11.data_index);
+	struct pkcs11_notify *notify = get_notify(ssl);
+	enum ssl_private_key_result_t ret = ssl_private_key_failure;
+
+	if (key_method != NULL && notify != NULL &&
+			pkcs11_notify_alloc(notify, max_out) &&
+			pkcs11_token_decrypt(key_method->token, notify, in, in_len))
+		ret = ssl_private_key_retry;
+	return ret;
+}
+
+static enum ssl_private_key_result_t pkcs11_complete(
+		SSL *ssl, uint8_t *out,
+		size_t *out_len, size_t max_out)
+{
+	struct pkcs11_notify *notify = get_notify(ssl);
+	enum ssl_private_key_result_t ret;
+
+	if (notify == NULL)
+		ret = ssl_private_key_failure;
+	else {
+		switch (pkcs11_notify_clear(notify, out, out_len, max_out)) {
+		case -1:
+			ret = ssl_private_key_retry;
+			break;
+		case 1:
+			ret = ssl_private_key_success;
+			break;
+		default:
+			ret = ssl_private_key_failure;
+			break;
+		}
+	}
+	return ret;
+}
+
+static SSL_PRIVATE_KEY_METHOD pkcs11_provider = {
+	.sign = pkcs11_sign,
+	.decrypt = pkcs11_decrypt,
+	.complete = pkcs11_complete,
+};
+#endif  /* BoringSSL, AWS-LC */
+
+static void ex_pkcs11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
+						   int index, long argl, void *argp)
+{
+	pkcs11_free((struct pkcs11_data *) ptr);
+}
+
+static void ex_notify_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
+						   int index, long argl, void *argp)
+{
+	struct pkcs11_notify *notify = (struct pkcs11_notify *) ptr;
+
+	if (notify)
+		pkcs11_notify_free(notify);
+}
+
+static int init_data_index(void)
+{
+	int ret = 0;
+
+	if (global_pkcs11.data_index == -1)
+		global_pkcs11.data_index = SSL_CTX_get_ex_new_index(
+			0, NULL, NULL, NULL, &ex_pkcs11_free);
+	if (global_pkcs11.data_index == -1)
+		ha_alert("pkcs11 : unable to register with SSL library: %s.\n",
+		         ERR_reason_error_string(ERR_get_error()));
+	else
+		ret = 1;
+	return ret;
+}
+
+static int init_pkcs11(void)
+{
+	int ret = ERR_NONE;
+
+	if (global_pkcs11.worker_threads) {
+		ret |= pkcs11_token_init(global_pkcs11.worker_threads);
+		if (ret != ERR_NONE)
+			goto out;
+
+		if (global_pkcs11.data_index == -1 && !init_data_index()) {
+			ret |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		global_pkcs11.ssl_index = SSL_get_ex_new_index(
+			0, NULL, NULL, NULL, &ex_notify_free);
+		if (global_pkcs11.ssl_index == -1) {
+			ha_alert("pkcs11 : unable to register with SSL library: %s.\n",
+					 ERR_reason_error_string(ERR_get_error()));
+			ret |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+
+out:
+	return ret;
+}
+
+static void deinit_pkcs11(void)
+{
+	if (global_pkcs11.worker_threads)
+		pkcs11_token_deinit();
+
+	global_pkcs11.worker_threads = 0;
+	if (global_pkcs11.default_library_path != NULL) {
+		free(global_pkcs11.default_library_path);
+		global_pkcs11.default_library_path = NULL;
+	}
+	global_pkcs11.data_index = -1;
+}
+
+REGISTER_POST_CHECK(init_pkcs11);
+REGISTER_POST_DEINIT(deinit_pkcs11);
+
+typedef struct provider_uri_st {
+	ASN1_VISIBLESTRING *description;
+	ASN1_UTF8STRING *uri;
+} PROVIDER_URI;
+
+ASN1_SEQUENCE(PROVIDER_URI) = {
+	ASN1_SIMPLE(PROVIDER_URI, description, ASN1_VISIBLESTRING),
+	ASN1_SIMPLE(PROVIDER_URI, uri, ASN1_UTF8STRING),
+} ASN1_SEQUENCE_END(PROVIDER_URI)
+
+IMPLEMENT_ASN1_FUNCTIONS(PROVIDER_URI)
+
+struct pkcs11_data *pkcs11_parse_pem(BIO *pem)
+{
+	char *name = NULL, *header = NULL;
+	unsigned char *data = NULL;
+	const unsigned char *ptr = NULL;
+	long len = 0;
+	PROVIDER_URI *parsed_provider = NULL;
+	struct pkcs11_data *provider = NULL;
+	struct pkcs11_uri *uri = NULL;
+	struct pkcs11_token *token = NULL;
+
+	/* it is invalid to load a PKCS#11 token when there's no worker threads */
+	if (global_pkcs11.worker_threads == 0)
+		goto out;
+	if (PEM_read_bio(pem, &name, &header, &data, &len) <= 0)
+		goto out;
+	if (strcmp(name, "PKCS#11 PROVIDER URI") != 0)
+		goto out;
+	ptr = data;
+	parsed_provider = d2i_PROVIDER_URI(NULL, &ptr, len);
+	if (parsed_provider == NULL || ptr != data + len)
+		goto out;
+	provider = calloc(1, sizeof(struct pkcs11_data));
+	if (provider == NULL)
+		goto out;
+	uri = pkcs11_uri_parse(ASN1_STRING_get0_data(parsed_provider->uri),
+	                       ASN1_STRING_length(parsed_provider->uri),
+	                       global_pkcs11.default_library_path);
+	if (uri == NULL)
+		goto err;
+	token = pkcs11_token_load(uri);
+	if (token == NULL)
+		goto err;
+	if (!pkcs11_token_get_prefs(token, provider->prefs, &provider->num_prefs,
+	                            sizeof(provider->prefs) / sizeof(provider->prefs[0])))
+		goto err;
+	provider->token = token;
+	token = NULL;
+
+out:
+	if (name)
+		OPENSSL_free(name);
+	if (header)
+		OPENSSL_free(header);
+	if (data)
+		OPENSSL_free(data);
+	if (parsed_provider)
+		PROVIDER_URI_free(parsed_provider);
+	if (uri)
+		pkcs11_uri_free(uri);
+	if (token)
+		pkcs11_token_free(token);
+	return provider;
+err:
+	if (provider) {
+		pkcs11_free(provider);
+		provider = NULL;
+	}
+	goto out;
+}
+
+int pkcs11_set_private_key(SSL_CTX *ctx, struct pkcs11_data *key_method)
+{
+#if !defined(OPENSSL_IS_AWSLC) && !defined(OPENSSL_IS_BORINGSSL)
+#error PKCS#11 is not implemented for this TLS framework.
+	return 0;
+#else
+	int ret = 0;
+
+	if (!init_data_index())
+		return ret;
+	ret = SSL_CTX_set_ex_data(
+		ctx, global_pkcs11.data_index, pkcs11_dup(key_method));
+	if (ret <= 0)
+		return ret;
+	ret = SSL_CTX_set_signing_algorithm_prefs(
+		ctx, key_method->prefs, key_method->num_prefs);
+	if (ret <= 0)
+		return ret;
+	SSL_CTX_set_private_key_method(ctx, &pkcs11_provider);
+	return 1;
+#endif
+}
+
+int pkcs11_check_private_key(X509 *cert, struct pkcs11_data *key_method)
+{
+	const EVP_PKEY *pubkey = X509_get0_pubkey(cert);
+
+	if (pubkey == NULL)
+		return 0;
+
+	/* TODO: Implement me. */
+	return 1;
+}
+
+void pkcs11_schedule_wakeup(SSL *ssl, struct wait_event *wait_event)
+{
+	struct pkcs11_notify *notify = get_notify(ssl);
+
+	if (notify == NULL)
+		tasklet_wakeup(wait_event->tasklet);
+	else
+		pkcs11_notify(notify, wait_event);
+}
+
+struct pkcs11_data *pkcs11_dup(struct pkcs11_data *key_method)
+{
+	struct pkcs11_data *duplicate = NULL;
+
+	if (key_method)
+		duplicate = calloc(1, sizeof(struct pkcs11_data));
+	if (duplicate) {
+		memcpy(duplicate->prefs, key_method->prefs, sizeof(key_method->prefs));
+		duplicate->num_prefs = key_method->num_prefs;
+		duplicate->token = pkcs11_token_dup(key_method->token);
+		if (duplicate->token == NULL) {
+			free(duplicate);
+			duplicate = NULL;
+		}
+	}
+	return duplicate;
+}
+
+void pkcs11_free(struct pkcs11_data *key_method)
+{
+	if (key_method) {
+		if (key_method->token) {
+			pkcs11_token_free(key_method->token);
+			key_method->token = NULL;
+		}
+		free(key_method);
+	}
+}
diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c
index 8e49fcb93..507314138 100644
--- a/src/ssl_ckch.c
+++ b/src/ssl_ckch.c
@@ -32,6 +32,7 @@
 #include <haproxy/channel.h>
 #include <haproxy/cli.h>
 #include <haproxy/errors.h>
+#include <haproxy/pkcs11.h>
 #include <haproxy/proxy.h>
 #include <haproxy/sc_strm.h>
 #include <haproxy/ssl_ckch.h>
@@ -404,7 +405,7 @@ int ssl_sock_load_files_into_ckch(const char *path, struct ckch_data *data, stru
 
 	}
 
-	if (data->key == NULL) {
+	if (data->key == NULL && data->key_method == NULL) {
 		/* If no private key was found yet and we cannot look for it in extra
 		 * files, raise an error.
 		 */
@@ -429,7 +430,7 @@ int ssl_sock_load_files_into_ckch(const char *path, struct ckch_data *data, stru
 			}
 		}
 
-		if (data->key == NULL) {
+		if (data->key == NULL && data->key_method == NULL) {
 			memprintf(err, "%sNo Private Key found in '%s'.\n", err && *err ? *err : "", fp->area);
 			goto end;
 		}
@@ -447,12 +448,20 @@ int ssl_sock_load_files_into_ckch(const char *path, struct ckch_data *data, stru
 	}
 
 
-	if (!X509_check_private_key(data->cert, data->key)) {
+	if (data->key && !X509_check_private_key(data->cert, data->key)) {
 		memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
 		          err && *err ? *err : "", path);
 		goto end;
 	}
 
+#ifdef USE_PKCS11
+	if (data->key_method && !pkcs11_check_private_key(data->cert, data->key_method)) {
+		memprintf(err, "%sinconsistencies between private key and certificate loaded '%s'.\n",
+		          err && *err ? *err : "", path);
+		goto end;
+	}
+#endif
+
 #ifdef HAVE_SSL_SCTL
 	/* try to load the sctl file */
 	if (global_ssl.extra_files & SSL_GF_SCTL) {
@@ -586,6 +595,7 @@ int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *d
 	BIO *in = NULL;
 	int ret = 1;
 	EVP_PKEY *key = NULL;
+	struct pkcs11_data *key_method = NULL;
 
 	if (buf) {
 		/* reading from a buffer */
@@ -608,14 +618,33 @@ int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *d
 	/* Read Private Key */
 	key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
 	if (key == NULL) {
+		if (BIO_reset(in) == -1) {
+			memprintf(err, "%san error occurred while reading the file '%s'.\n",
+					  err && *err ? *err : "", path);
+			goto end;
+		}
+		goto pkcs11;
+	}
+
+	ret = 0;
+
+	SWAP(data->key, key);
+	goto end;
+
+pkcs11:
+#ifdef USE_PKCS11
+	/* Read PKCS#11 provider */
+	key_method = pkcs11_parse_pem(in);
+#endif /* USE_PKCS11 */
+	if (key_method == NULL) {
 		memprintf(err, "%sunable to load private key from file '%s'.\n",
-		          err && *err ? *err : "", path);
+			      err && *err ? *err : "", path);
 		goto end;
 	}
 
 	ret = 0;
 
-	SWAP(data->key, key);
+	SWAP(data->key_method, key_method);
 
 end:
 
@@ -624,6 +653,10 @@ int ssl_sock_load_key_into_ckch(const char *path, char *buf, struct ckch_data *d
 		BIO_free(in);
 	if (key)
 		EVP_PKEY_free(key);
+#ifdef USE_PKCS11
+	if (key_method)
+		pkcs11_free(key_method);
+#endif /* USE_PKCS11 */
 
 	return ret;
 }
@@ -644,6 +677,7 @@ int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct ckch_data *d
 	X509 *ca;
 	X509 *cert = NULL;
 	EVP_PKEY *key = NULL;
+	struct pkcs11_data *key_method = NULL;
 	HASSL_DH *dh = NULL;
 	STACK_OF(X509) *chain = NULL;
 	struct issuer_chain *issuer_chain = NULL;
@@ -673,7 +707,22 @@ int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct ckch_data *d
 
 	/* Read Private Key */
 	key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
-	/* no need to check for errors here, because the private key could be loaded later */
+
+	/* If the key could not be read, then check it wasn't a PKCS#11 provider */
+	if (key == NULL) {
+		/* Seek back to beginning of file */
+		if (BIO_reset(in) == -1) {
+			memprintf(err, "%san error occurred while reading the file '%s'.\n",
+					  err && *err ? *err : "", path);
+			goto end;
+		}
+
+#ifdef USE_PKCS11
+		/* Read PKCS#11 provider */
+		key_method = pkcs11_parse_pem(in);
+#endif /* USE_PKCS11 */
+		/* no need to check for errors here, because the private key could be loaded later */
+	}
 
 #ifndef OPENSSL_NO_DH
 	/* Seek back to beginning of file */
@@ -746,6 +795,7 @@ int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct ckch_data *d
 
 	/* no error, fill data with new context, old context will be free at end: */
 	SWAP(data->key, key);
+	SWAP(data->key_method, key_method);
 	SWAP(data->dh, dh);
 	SWAP(data->cert, cert);
 	SWAP(data->chain, chain);
@@ -760,6 +810,10 @@ int ssl_sock_load_pem_into_ckch(const char *path, char *buf, struct ckch_data *d
 		BIO_free(in);
 	if (key)
 		EVP_PKEY_free(key);
+#ifdef USE_PKCS11
+	if (key_method)
+		pkcs11_free(key_method);
+#endif /* USE_PKCS11 */
 	if (dh)
 		HASSL_DH_free(dh);
 	if (cert)
@@ -787,6 +841,13 @@ void ssl_sock_free_cert_key_and_chain_contents(struct ckch_data *data)
 		EVP_PKEY_free(data->key);
 	data->key = NULL;
 
+#ifdef USE_PKCS11
+	/* Free the key method and set pointer to NULL */
+	if (data->key_method)
+		pkcs11_free(data->key_method);
+	data->key_method = NULL;
+#endif /* USE_PKCS11 */
+
 	/* Free each certificate in the chain */
 	if (data->chain)
 		sk_X509_pop_free(data->chain, X509_free);
@@ -858,6 +919,11 @@ struct ckch_data *ssl_sock_copy_cert_key_and_chain(struct ckch_data *src,
 		EVP_PKEY_up_ref(src->key);
 	}
 
+#ifdef USE_PKCS11
+	if (src->key_method)
+		dst->key_method = pkcs11_dup(src->key_method);
+#endif /* USE_PKCS11 */
+
 	if (src->chain) {
 		dst->chain = X509_chain_up_ref(src->chain);
 	}
@@ -2412,7 +2478,7 @@ static int cli_io_handler_dump_cert(struct appctx *appctx)
 	if ((bio = BIO_new(BIO_s_mem())) ==  NULL)
 		goto end_no_putchk;
 
-	if (index == -2) {
+	if (index == -2 && ckchs->data->key) {
 
 		if (BIO_reset(bio) == -1)
 			goto end_no_putchk;
@@ -2431,6 +2497,9 @@ static int cli_io_handler_dump_cert(struct appctx *appctx)
 
 		index++;
 
+	} else if (index == -2) {
+		// TODO: Dump PKCS#11 provider details.
+		index++;
 	}
 
 	if (index == -1) {
@@ -2805,16 +2874,26 @@ static int cli_parse_commit_cert(char **args, char *payload, struct appctx *appc
 	}
 
 	/* if a certificate is here, a private key must be here too */
-	if (ckchs_transaction.new_ckchs->data->cert && !ckchs_transaction.new_ckchs->data->key) {
+	if (ckchs_transaction.new_ckchs->data->cert && (!ckchs_transaction.new_ckchs->data->key ||
+		                                            !ckchs_transaction.new_ckchs->data->key_method)) {
 		memprintf(&err, "The transaction must contain at least a certificate and a private key!\n");
 		goto error;
 	}
 
-	if (!X509_check_private_key(ckchs_transaction.new_ckchs->data->cert, ckchs_transaction.new_ckchs->data->key)) {
+	if (ckchs_transaction.new_ckchs->data->key &&
+		!X509_check_private_key(ckchs_transaction.new_ckchs->data->cert, ckchs_transaction.new_ckchs->data->key)) {
 		memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
 		goto error;
 	}
 
+#ifdef USE_PKCS11
+	if (ckchs_transaction.new_ckchs->data->key_method &&
+		!pkcs11_check_private_key(ckchs_transaction.new_ckchs->data->cert, ckchs_transaction.new_ckchs->data->key_method)) {
+		memprintf(&err, "inconsistencies between private key and certificate loaded '%s'.\n", ckchs_transaction.path);
+		goto error;
+	}
+#endif
+
 	/* init the appctx structure */
 	ctx->state = CERT_ST_INIT;
 	ctx->next_ckchi = NULL;
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 15ca095e9..e9f792a05 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -61,6 +61,7 @@
 #include <haproxy/log.h>
 #include <haproxy/openssl-compat.h>
 #include <haproxy/pattern-t.h>
+#include <haproxy/pkcs11.h>
 #include <haproxy/proto_tcp.h>
 #include <haproxy/proxy.h>
 #include <haproxy/quic_conn.h>
@@ -2740,7 +2741,11 @@ static int ssl_sock_put_ckch_into_ctx(const char *path, struct ckch_store *store
 
 	ERR_clear_error();
 
-	if (SSL_CTX_use_PrivateKey(ctx, data->key) <= 0) {
+	if ((data->key && SSL_CTX_use_PrivateKey(ctx, data->key) <= 0)
+#ifdef USE_PKCS11
+			|| (data->key_method && pkcs11_set_private_key(ctx, data->key_method) <= 0)
+#endif /* USE_PKCS11*/
+			) {
 		int ret;
 
 		ret = ERR_get_error();
@@ -2820,7 +2825,11 @@ static int ssl_sock_put_srv_ckch_into_ctx(const char *path, const struct ckch_da
 	STACK_OF(X509) *find_chain = NULL;
 
 	/* Load the private key */
-	if (SSL_CTX_use_PrivateKey(ctx, data->key) <= 0) {
+	if ((data->key && SSL_CTX_use_PrivateKey(ctx, data->key) <= 0)
+#ifdef USE_PKCS11
+			|| (data->key_method && pkcs11_set_private_key(ctx, data->key_method) <= 0)
+#endif /* USE_PKCS11*/
+			) {
 		memprintf(err, "%sunable to load SSL private key into SSL Context '%s'.\n",
 				err && *err ? *err : "", path);
 		errcode |= ERR_ALERT | ERR_FATAL;
@@ -5345,6 +5354,13 @@ static int ssl_sock_handshake(struct connection *conn, unsigned int flag)
 				}
 				goto out_error;
 			}
+#if defined(USE_PKCS11) && (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC))
+			else if (ret == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+				/* Waiting on a PKCS#11 operation, schedule a retry. */
+				pkcs11_schedule_wakeup(ctx->ssl, &ctx->wait_event);
+				return 0;
+			}
+#endif /* BoringSSL or AWS-LC */
 			else {
 				/* Fail on all other handshake errors */
 				/* Note: OpenSSL may leave unread bytes in the socket's
@@ -5440,6 +5456,13 @@ static int ssl_sock_handshake(struct connection *conn, unsigned int flag)
 			goto out_error;
 
 		}
+#if defined(USE_PKCS11) && (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC))
+		else if (ret == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+			/* Waiting on a PKCS#11 operation, schedule a retry. */
+			pkcs11_schedule_wakeup(ctx->ssl, &ctx->wait_event);
+			return 0;
+		}
+#endif /* BoringSSL or AWS-LC */
 		else {
 			/* Fail on all other handshake errors */
 			/* Note: OpenSSL may leave unread bytes in the socket's
@@ -5869,6 +5892,14 @@ static size_t ssl_sock_to_buf(struct connection *conn, void *xprt_ctx, struct bu
 					ctx->error_code = ERR_peek_error();
 				conn->err_code = CO_ER_SSL_FATAL;
 			}
+#if defined(USE_PKCS11) && (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC))
+			else if (ret == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+				/* Waiting on a PKCS#11 operation, schedule a retry. */
+				conn->flags |= CO_FL_SSL_WAIT_HS;
+				pkcs11_schedule_wakeup(ctx->ssl, &ctx->wait_event);
+				break;
+			}
+#endif /* BoringSSL or AWS-LC */
 			/* For SSL_ERROR_SYSCALL, make sure to clear the error
 			 * stack before shutting down the connection for
 			 * reading. */
@@ -6047,6 +6078,14 @@ static size_t ssl_sock_from_buf(struct connection *conn, void *xprt_ctx, const s
 					ctx->error_code = ERR_peek_error();
 				conn->err_code = CO_ER_SSL_FATAL;
 			}
+#if defined(USE_PKCS11) && (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC))
+			else if (ret == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+				/* Waiting on a PKCS#11 operation, schedule a retry. */
+				conn->flags |= CO_FL_SSL_WAIT_HS;
+				pkcs11_schedule_wakeup(ctx->ssl, &ctx->wait_event);
+				break;
+			}
+#endif /* BoringSSL or AWS-LC */
 			goto out_error;
 		}
 	}
