diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index 9dfa6bd..7a09809 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -489,6 +489,10 @@
   the origin of the connection. Each generated file will be automatically
   deleted when the proxied connection is torn down.
 
+  The files in ``dir`` are created and removed with the privileges of the
+  OpenVPN process, so ``dir`` must not be writable by any user other than
+  the one OpenVPN runs as.
+
   Not implemented on Windows.
 
 --push option
diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c
index 03c6f48..f8cfe1c5 100644
--- a/src/openvpn/ps.c
+++ b/src/openvpn/ps.c
@@ -341,7 +341,15 @@
         check_malloc_return(jfn);
         snprintf(jfn, fnlen, "%s/%s", journal_dir, t);
         dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: client origin %s -> %s", jfn, f);
-        int fd = platform_open(jfn, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP);
+        /* never write through a pre-existing symlink or hardlink */
+        const int flags = O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW;
+        int fd = platform_open(jfn, flags, S_IRUSR | S_IWUSR | S_IRGRP);
+        if (fd == -1 && errno == EEXIST)
+        {
+            /* stale entry, e.g. left behind by a killed proxy process */
+            unlink(jfn);
+            fd = platform_open(jfn, flags, S_IRUSR | S_IWUSR | S_IRGRP);
+        }
         if (fd != -1)
         {
             ssize_t write_len = strlen(f);
