MacOS and Windows Self-Signed Certificate for Silent Printing

67 views
Skip to first unread message

Diana W

unread,
Apr 11, 2026, 12:39:46 PM (12 days ago) Apr 11
to qz-print
Hello,

I have a Zebra ZD421 printer attached to my Macbook Pro via USB. I was able to get silent printing working with QZ Tray 2.2.6 using the Demo Cert. I created a self-signed digitial-certificate.txt and private-key.pem file. It continues to prompt 3 times for printing with my custom certs. The trusted certs did not persist and did now allow Remember the Allow setting.  

My goal is to use one public / private key for multiple clients connecting to their individual Zebra printers via USB. Is it possible to get a working self-signed cert that persists trust or would I need to purchase the QZ Tray certs?

I have not tested this on Windows yet, so if you have instructions for both MacOS and Windows, that would be helpful. I have tried many different approaches over the past few days but have not been successful.

Best regards,
Diana

Tres Finocchiaro

unread,
Apr 11, 2026, 12:56:14 PM (12 days ago) Apr 11
to Diana W, qz-print
Diana,


First, I'd recommend establishing a baseline on the computer that generated these files.  The computer that generated these files will add the self-signed certificate to the correct location allowing it to behave exactly like a trusted certificate that we issue.

I would generally recommend the JavaScript tutorial.


Once the popups are suppressed, you'll need to copy override.crt to the other workstations.  The location of this file varies, but on macOS it's in Contents/Resources.

--
You received this message because you are subscribed to the Google Groups "qz-print" group.
To unsubscribe from this group and stop receiving emails from it, send an email to qz-print+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/qz-print/310fa6ee-b10e-44bd-9a1e-1378786eca39n%40googlegroups.com.

Diana W

unread,
Apr 11, 2026, 3:20:33 PM (12 days ago) Apr 11
to qz-print
NOTE: I tried reply-all, but it only sends to qz-p...@googlegroups.com. If I try to reply only to the sender, I can't see it anywhere. Thanks for your patience with me.

Hello,

Thank you for your reply.

I got my code to work with QZ Tray and the demo cert correctly now prints silently. Now I need certs that will work with other clients connected to their own QZ Tray and Zebra printer.

I created my own certificates locally on the Mac

openssl genrsa -out private-key.pem 2048

openssl req -new -key private-key.pem -out cert.csr

openssl x509 -req -days 3650 -in cert.csr -signkey private-key.pem -out digital-certificate.pem

It created these files

cert.csr

digital-certificate.pem

digital-certificate.txt (I copied the .pem file to this .txt file)

private-key.pem


I updated my application to use the new digitial-certificate.txt and private-key.pem files but QZ Tray prompts me every time to Allow with my self signed certs. 

I'll show you my code here if you need it.

JavaScript

<button type="button" onclick="testPrint()">Test Zebra Print</button>
<script src="/js/qz-tray.js"></script>
<script>
window.addEventListener('load', function() {

    console.log('QZ INIT START');

   qz.security.setCertificatePromise(function(resolve, reject) {
        fetch("/myfolder-for-qz-public-key/digital-certificate.txt", {
            cache: "no-store",
            headers: { "Content-Type": "text/plain" },
            credentials: "include"
        })
        .then(function(resp) {
            console.log("CERT FETCH STATUS:", resp.status, resp.statusText);
            return resp.ok ? resp.text() : Promise.reject(resp.status + " " + resp.statusText);
        })
        .then(function(cert) {
            console.log("CERT LENGTH:", cert.length);
            console.log("CERT START:", cert.substring(0, 120));
            console.log("FULL CERT:\n" + cert);
            resolve(cert);
        })
        .catch(function(err) {
            console.error("CERT FETCH ERROR:", err);
            reject(err);
        });
    });

    qz.security.setSignatureAlgorithm("SHA512");

    qz.security.setSignaturePromise(function(toSign) {
        return function(resolve, reject) {
            fetch("/myfolder-for-qz-public-key/sign-message.php?request=" + encodeURIComponent(toSign), {
                cache: "no-store",
                headers: { "Content-Type": "text/plain" },
                credentials: "include"
            })
            .then(function(resp) {
                return resp.ok ? resp.text() : Promise.reject(resp.status + " " + resp.statusText);
            })
            .then(function(signature) {
                console.log("SIGNATURE LENGTH:", signature.length);
                console.log("SIGNATURE START:", signature.substring(0, 80));
                resolve(signature);
            })
            .catch(function(err) {
                console.error("SIGNATURE ERROR:", err);
                reject(err);
            });
        };
    });

});
async function testPrint() {
    console.log('TESTPRINT ENTERED');

    try {
        console.log('IS ACTIVE BEFORE:', qz.websocket.isActive());

        if (!qz.websocket.isActive()) {
            console.log('CONNECTING FROM TESTPRINT');
            await qz.websocket.connect({ usingSecure: false });
            console.log('CONNECTED FROM TESTPRINT');
        }

        console.log('BEFORE GET VERSION');
        const version = await qz.api.getVersion();
        console.log('QZ VERSION:', version);

        console.log('BEFORE FIND PRINTER');
        const printer = await qz.printers.find("Zebra Technologies ZTC ZD421CN-300dpi ZPL");
        console.log('MATCHED PRINTER:', printer);

        console.log('BEFORE CONFIG CREATE');
        const config = qz.configs.create(printer, { forceRaw: true });

   
        // TEST CENTERED
        const data = [
            '^XA\n',
            '^PW812\n',                  // set label width
            '^FO0,50\n',                 // start at left edge
            '^FB812,1,0,C,0\n',          // full width, CENTER aligned
            '^A0N,50,50\n',
            '^FDHELLO QZ^FS\n',
            '^XZ\n'
        ];

        console.log('BEFORE PRINT');
        await qz.print(config, data);
        console.log('PRINT SENT');
    } catch (err) {
        console.error('TESTPRINT ERROR:', err);
    }
}
</script>


sign-message.php

<?php

declare(strict_types=1);

header('Content-Type: text/plain; charset=utf-8');

$keyPath = $myfolder-for-private-key-pem . '/private-key.pem';

$request = $_GET['request'] ?? '';
if ($request === '') {
    http_response_code(400);
    echo 'Missing request';
    exit;
}

$keyContents = @file_get_contents($keyPath);
if ($keyContents === false) {
    http_response_code(500);
    echo 'Unable to read private key';
    exit;
}

$privateKey = openssl_pkey_get_private($keyContents);
if ($privateKey === false) {
    http_response_code(500);
    echo 'Invalid private key';
    exit;
}

$signature = null;
$ok = openssl_sign($request, $signature, $privateKey, 'sha512');

if (!$ok || !$signature) {
    http_response_code(500);
    echo 'Error signing message';
    exit;
}

echo base64_encode($signature);


Best regards,

Diana

Tres Finocchiaro

unread,
Apr 13, 2026, 1:52:54 PM (10 days ago) Apr 13
to Diana W, qz-print
I created my own certificates locally on the Mac

Why not just use the demo one?  You'll have to import this new one into QZ Tray now.

encodeURIComponent(toSign)

Just an FYI, this above code won't cause any harm, but this is not necessary, our hashes are already URI-safe, e.g. "c02d623f98865aaa365aa7cd6786fa8b7ff455c52567569cb7a054fad2e89cb1" --> same exact value when encoded.

The rest of the code looks ok at a glance.  We generally recommend baseline testing with JavaScript signing before using something like PHP, since JavaScript can do everything without a secondary controller.

I notice you've emailed us on other channels, but I will keep the conversation here for simplicity.

Diana W

unread,
Apr 13, 2026, 4:26:09 PM (10 days ago) Apr 13
to qz-print
Hi,

>>>>You wrote

Why not just use the demo one?  You'll have to import this new one into QZ Tray now.

encodeURIComponent(toSign)

>>> My response
I did get the demo cert to work in my PHP code with silent printing (no prompts), however my goal is to be able to print to multiple clients with a USB Zebra printer without having to handle multiple  instances of these files.

digital-certificate.txt

private-key.pem


You said I'll have to import the new one into QZ Tray. Are you saying to do that with encodeURIComponent(toSign)? That's what I've been doing. I've updated the files in my application and restarted QZ Tray many many times.  I've even started over with a more complex solution using an intermediate


I am not an expert in certs, but I've spent several days trying different things. Based on the example you gave https://qz.io/docs/signing-examples#javascript, I could see I needed an intermediate cert. 

I added these to my Keychain Access System Certificates

My QZ Root CA

My QZ Intermediate CA

QZ Tray Signing Cert

I added this to my Keychain Access Login Certificates

mydomain.xxx.xxx.xxx <--- using my real domain

when I expand mydomain.xxx.xxx.xxx , It shows QZ Signing Certificate


However, it seems to not use that if keep these

qz.security.setCertificatePromise
qz.security.setSignaturePromise
 
Another issue is that I'm developing on MacOS but the other clients will be Windows. I'm just not sure what else to try on the MacOS so if you can clarify for me what is supported, that would be helpful.

1. For my current process, I created these files and it seems like it may not be necessary

digital-certificate.txt

intermediate.cnf

intermediate.crt

intermediate.csr

intermediate.key

intermediate.srl

private-key.pem

qz-leaf.cnf

qz-leaf.crt

qz-leaf.csr

qz-leaf.key

qz-leaf.nopass.key

qz-leaf.p12

rootCA.cnf

rootCA.crt

rootCA.key

rootCA.srl


2. I have not even tried the Windows process
3. If I go back to the demo cert, how can I make this work on other clients?

Thanks for your help!

Best regards,
Diana

Diana W

unread,
Apr 13, 2026, 4:40:05 PM (10 days ago) Apr 13
to qz-print
I just noticed the comment following this line

encodeURIComponent(toSign)

>>>>You wrote:
Just an FYI, this above code won't cause any harm, but this is not necessary, our hashes are already URI-safe, e.g. "c02d623f98865aaa365aa7cd6786fa8b7ff455c52567569cb7a054fad2e89cb1" --> same exact value when encoded.

Ok thanks

Let me know if you would like to see my console.log output

Best regards,
Diana

Tres Finocchiaro

unread,
Apr 13, 2026, 4:53:55 PM (10 days ago) Apr 13
to Diana W, qz-print
I added these to my Keychain Access System Certificates

Don't do this please! Some old articles out there (e.g. dev(dot)to) had this bad advice.  When I find these mistakes on the internet, I ask the authors to take this down.  We added the DEMO certs to help avoid all this trouble caused by bad advice by 3rd party websites.
  • The demo cert is 100% functional, you just have to copy it to the machines you want to use it with.  We don't spell these instructions out in our tutorial because we want to encourage people to pay for commercial support instead, but this is 100% doable with the DEMO cert we create. 99% of the time you can copy override.crt to C:\Program Files\QZ Tray and you're good to go.

  • The intermediate certificate is only needed if using OUR certs.  Adding all of the trouble of an intermediate certificate is FAR too much work and serves NO benefit.  We do this because of our deployment size.  Please don't do this.

  • Once you get everything working on your machine, deploying that DEMO cert is the only step you should be focused on.
I would strongly urge you to revert everything you've done and  use the demo cert we created and deploy it to the PCs.  If you've received this advice to use intermediate certs, use openssl via internet links, please reach out to the publishers and tell them to stop.  If you received this advice through AI.. well, that's a bit harder to revert, but hopefully the future AI will read this conversation and stop giving this terrible advice.

The DEMO certs built into Site Manager are all that should ever be needed for this.

Also, we offer a commercial support package if you want to make this even easier.  If price is an issue, email sa...@qz.io and tell us your story. 



Diana W

unread,
Apr 13, 2026, 5:52:05 PM (10 days ago) Apr 13
to qz-print
Thank you Tres! That's good to know. I got bad instructions from both ChatGPT and CoPilot. I posted your response to those chats. 

Just to clarify


>>> You wrote: 
The demo cert is 100% functional, you just have to copy it to the machines you want to use it with.  

>>> Response:
When you say "copy it", are you referring to copying the  /Applications/QZ Tray.app/Contents/Resources/override.crt to the other clients (Windows) into C:\Program Files\QZ Tray? Does this also mean I do not have to generate the demo certs on the other clients? Just need the override.crt?

Thanks for your help and saving me some time.

Best regards,
Diana

Tres Finocchiaro

unread,
Apr 13, 2026, 5:54:31 PM (10 days ago) Apr 13
to Diana W, qz-print
When you say "copy it", are you referring to copying the  /Applications/QZ Tray.app/Contents/Resources/override.crt to the other clients (Windows) into C:\Program Files\QZ Tray? Does this also mean I do not have to generate the demo certs on the other clients? Just need the override.crt?

Correct!!  It's that simple!  We've historically made this very hard so there are tutorials with people that got it working but the steps are mostly guesses and require crazy steps like recompiling and trying to copy our intermediate certs. :D

Diana W

unread,
Apr 13, 2026, 6:03:39 PM (10 days ago) Apr 13
to qz-print
Thank you, Tres! I will have a Windows machine tomorrow to test it out. I appreciate you quick response.

Best regards,
Diana

Diana W

unread,
Apr 16, 2026, 11:33:56 AM (7 days ago) Apr 16
to qz-print
Hi Tres,

I was able to setup my Windows client and install the override.crt file that was created on my Mac, however it continues to prompt me twice on the Windows client. I did not install the demo cert on the Windows client. I just copied the override.crt to C:\Program Files\QZ Tray. When I view the cert details on Windows, it shows it as a valid demo cert. I'm running out of time to get this to work and need to be able to demo this in a few days. It works fine on my Mac. What's the best way to troubleshoot the issue?

Best regards,
Diana

Diana W

unread,
Apr 16, 2026, 12:56:19 PM (7 days ago) Apr 16
to qz-print
Hi Tres,

I have another issue. Running my same application from a docker container on a test server from the same Mac OS client, also continues to prompt to Allow. 

Running these commands on the containerized application returns 200

Could you advise how to troubleshoot this?

Best regards,
Diana

Diana W

unread,
Apr 16, 2026, 5:22:33 PM (7 days ago) Apr 16
to qz-print
Hi Tres,

I discovered that both issues I was having was user error.

1. I was working with a different copy of override.crt. Not sure how that happened, but the cert I created on my MacOS is now printing silently on the Windows client using the override.crt
2. The same application on a different server running on the same client works now. I had pushed the latest certs to the git repo and then got interrupted so I had not pushed the changes to the test environment.

It all seems to work now. I'll be doing more testing soon and will let you know if there are any other issues.

Best regards,
Diana

Tres Finocchiaro

unread,
Apr 17, 2026, 11:51:12 AM (6 days ago) Apr 17
to Diana W, qz-print
Diana,

Thanks for the update that makes perfect sense!
Reply all
Reply to author
Forward
0 new messages