How to get your Unity LLAPI/WebSocket WebGL app to run under https with AutoSSL & stunnel

<continuing my “blog about whatever random issue I last dealt with in the hopes that some poor soul with the same issue will google it one day” series>

The problem

So you made your new Unity webGL game using the LLAPI and it works fine from a http:// address.  But when you try with https, even with a valid https cert being installed, you get this error:

“Uncaught SecurityError: Failed to construct ‘WebSocket’: An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.”

This is your browser saying “Look, the website is https, but don’t let that fool you; it’s using a normal old web socket to send data under the hood which isn’t encrypted, so don’t trust this thing with your credit card numbers”.

Unity (at the time of this writing) has no internal support for what we really need to be using:  a Secure Web Socket.  So where http has https, ws has wss.  So how do we connect securely if our unity-based server binary can’t serve wss directly?

A little background info about CPanel & AutoSSL

Note: I’m using CentOS 7 on a dedicated server with WHM/CPanel

Setting up your website for proper SSL so it can have that wonderful green padlock used to be a painful and sometimes expensive ordeal.

But no longer!  Enter the magic of CPanel’s AutoSSL.  (I think it’s using Let’s Encrypt under the hood as a plugin?)  Behind the scenes, it will handle domain validation and setup everything for you.  While it does need to renew your cert every three months, it’s free and automatic.  Add four new domains?  They will all get valid certs within a day or so, it’s great.

We can use this same cert to make your websockets secure as long as they are hosted at the same domain.

Setting up stunnel

This is an open source utility that is likely already included on your linux server box, if it isn’t, go install it with yum or something.

It allows you to convert any socket into a secure socket.  For example, if you have a telnet port at 1000, you could setup stunnel to listen at 1001 securely and relay all information back to 1000.

The telnet connection has no idea what’s happening and sees no difference, but as long as the outside user can only access 1001, plain text information isn’t sent along the wire and one or both sides can be sure of the identity of who’s connecting.

Depending on the stunnel settings, it might be setup like https where the client doesn’t have to have any certain keys (what we want here), or it could be like a ssh where the client DOES need a whitelisted key.

A way to test a SSL port is to use OpenSSL from the command line on the host server via ssh.  For example (keep in mind 443 is the standard https port your website is probably using):

<at ssh prompt> openssl s_client -connect localhost:443

<info snipped>
subject=/OU=Domain Control Validated/OU=PositiveSSL/
issuer=/C=US/ST=TX/L=Houston/O=cPanel, Inc./CN=cPanel, Inc. Certification Authority
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
SSL handshake has read 4946 bytes and written 415 bytes
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
 Protocol : TLSv1.2
<info snipped>
Start Time: 1518495864
 Timeout : 300 (sec)
 Verify return code: 0 (ok)

Hitting enter after that will probably cause the website to an html error message because we didn’t send a valid request. That’s ok, it shows your website’s existing SSL stuff is working so we can move on.

So first edit your /etc/stunnel/stunnel.conf to something like this:

pid = /etc/stunnel/

#we won't screw with changing this because we don't want to relocate/change permissions on our files right now
#setuid = nobody
#setgid = nobody

sslVersion = all
options = NO_SSLv2

#for testing purposes.. these should be removed later:
output = /etc/stunnel/log.txt
foreground = yes
debug = 7

accept = 29000
connect = 80
cert = /var/cpanel/ssl/apache_tls/

accept = 30000
connect = 20000
cert = /var/cpanel/ssl/apache_tls/

Next, still from the ssh prompt, run stunnel by typing stunnel.

Because we have foreground=yes set above it will run it in the shell, showing us all output directly, instead of in the background like it normally would. (Ctrl-C to cause stunnel to stop and quit)

Look for any issues or errors it reports.  The .conf file I listed aboveshows how to set it up for two or more tunnels at once, you likely only need one of those settings.

The “websitename1” part doesn’t matter or have to match anything.

The SSL cert is the most important setting.  You need to give it your private & public & CA info in  the same file.

Now, initially, you might try to setup your keys using the files in ~/ssl/keys and ~/ssl/certs but they seem to not have everything all in one nice file including the CA certs.  I figured out ‘bundled’ ones already exist in a cpanel directory so I linked straight to them there.  (replace with your website name)

If stuff worked, you should be able to test your SSL’ed port with OpenSSL again.  In the example above under “websitename1” I told it to listen at 29000 and send to port 80, for no good reason.

So to test from a remote computer we can do:

(you did open those ports in your firewall so outside people can connect, right?)

C:\Users\Seth>openssl s_client -connect
Loading 'screen' into random state - done
depth=2 /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
verify error:num=20:unable to get local issuer certificate
verify return:0
Certificate chain
 0 s:/
 i:/C=US/ST=TX/L=Houston/O=cPanel, Inc./CN=cPanel, Inc. Certification Authority
 1 s:/C=US/ST=TX/L=Houston/O=cPanel, Inc./CN=cPanel, Inc. Certification Authority
 i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
Server certificate
issuer=/C=US/ST=TX/L=Houston/O=cPanel, Inc./CN=cPanel, Inc. Certification Authority
No client certificate CA names sent
SSL handshake has read 5129 bytes and written 453 bytes
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
 Protocol : TLSv1
 Key-Arg : None
 Start Time: 1518497616
 Timeout : 300 (sec)
 Verify return code: 20 (unable to get local issuer certificate)

Despite the errno=11093 and return code 20 errors, it’s working and properly sending our CA info (“cPanel, Inc. Certification Authority”).

Or, easier, let’s just use the browser instead for this one since we’re connecting to port 80 if it works in this case:

It worked, see the green padlock?  Oh, ignore the error the website is sending, I assume that’s apache freaking out because the URL request is different from what it’s expecting (http vs https or the port difference?) so it can’t match up the virtual domain.

From here, you should probably remove the debug options in the .conf (including the foreground=yes) and set it up to run automatically.  I just placed “stunnel” in my /etc/rc.d/rc.local file. (this gets run at boot)

Actually connecting using the Unity LLAPI

Congratulations, everything is setup on the server and you’re sure your web socket port is listening and ready to go.

While your server binary doesn’t need to change anything, your webgl client does.

You now need to connect to WSS instead of WS.  Example:

   _connectionID = NetworkTransport.Connect(_hostID, "wss://", portNum, 0, out error);
 catch (System.Exception ex)
   Debug.Log("RTNetworkClient.Connect> " + ex.Message);

That’s pretty much it.  If someone doesn’t care about https and decides to play over http, it still works fine. (internally the websocket code will still connect via wss)

If you want to see it in action, check out my webgl llapi multiplayer test project

Unity snippet: Finding a GameObject by name, even inactive or disabled ones

I use GameObject.Find() in Unity for things like enabling or fading in/out a menu or to grab an object reference via code to store for later.   (I usually prefer doing things in code rather than drag and dropping references using the Unity Editor when I can)

A problem is GameObject.Find() won’t locate inactive gameobjects which causes me problems because I tend to have inactive object trees in a scene that are just turned on/off when they are being used, like a GUI menu for example.  It’s just kind of my programming style to do things that way.

I couldn’t find a clean full snippet for this online that used scene.GetRootGameObjects, so figured I’d post one.

Cut and paste this to MyUtils.cs or your own utils class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MyUtils 

    //hideously slow as it iterates all objects, so don't overuse!
    public static GameObject FindInChildrenIncludingInactive(GameObject go, string name)

        for (int i = 0; i < go.transform.childCount; i++)
            if (go.transform.GetChild(i) == name) return go.transform.GetChild(i).gameObject;
            GameObject found = FindInChildrenIncludingInactive(go.transform.GetChild(i).gameObject, name);
            if (found != null) return found;

        return null;  //couldn't find crap

    //hideously slow as it iterates all objects, so don't overuse!
    public static GameObject FindIncludingInactive(string name)
        Scene scene = SceneManager.GetActiveScene();
        var game_objects = new List();

        foreach (GameObject obj in game_objects)
            GameObject found = FindInChildrenIncludingInactive(obj, name);
            if (found) return found;

        return null;


And use it from anywhere like:

GameObject obj = MyUtil.FindIncludingInactive(“MyMenuName”);

My PUBG story: Someone is shooting at me

The RTsoft PUBG squad both online and IRL.  Akiko, June, Cosmo.  Pic taken by Seth

A true story from PUBG

“I think I hear footsteps outside” Akiko whispered.  I was confident we’d be safe, at least for a while, in the mountain shack we’d found.  Our blessed respite from the cruel world of PUBG was about to be shattered.

“Stay here, I’ll check it out”.  I opened the door and creeped around the outside of the building.  “They think they can come here and threaten… ” I didn’t get a chance to finish my thought as it was unceremoniously interrupted by a shotgun blast to the back of my head.

Akiko screamed as she watched through the window. I fell to my knees and tried in vain to crawl back to the door.  He stood over me, gun in hand, preparing a second shot to end my suffering.

But the shot didn’t come.  He’d noticed movement inside the house.  The bastard turned his attention toward my wife and there was nothing I could do about it.

In a panic, hands shaking, Akiko burst from the cabin firing wildly.  But alas, her bullets did not meet their intended target.

The cutthroat returned fire and brutally put her down.  I collapse only inches from her sprawled body.  She died trying to save me.

Why PUBG is good

PUBG (and the survival/battle royale rules that Brendan Greene and others have developed and tweaked) breaks with tradition in a lot of ways:

  • There is no story (other than your own)
  • There is no voice acting (other than the occasional grunt)
  • There are no cut scenes
  • There is no text chat
  • Name labels are not drawn over enemies
  • A single round can last up to 35 minutes
  • Matchmaker ignores skill/ratings and just puts everybody together
  • It can be unfair.  It’s not designed to be fair

You are dumped into a large open world with random loot and vehicle placements.  It supports varied play styles, you can rambo it up and shoot everyone, or be stealthy and win without firing a shot.  There really isn’t a wrong way to play.

A big part of the allure is the variety of situations that can occur due to randomness.  No two games are alike.  The scavenging aspect is a form of slot machine gambling (the good kind, not to be confused with money sucking loot crates), will you find that 8x scope in that bathroom or just another pair of shoes?

If you can find the right pieces for your gun, you can sort of create a matching set that gives you an advantage.  Looting more houses gives you more lottery tickets to scratch.

In some ways it takes inspiration from games like FTL or Weird Worlds: Return to Infinite Space in that random loot drives a game that lasts less than an hour.  This combined with solid FPS gunplay and huge worlds (the “can’t find other players” problem has been neatly solved by an ever-shrinking playfield) present an amazing experience.

Loot crate controversy!

Jim Sterling is doing the Lord’s work by calling out the recent crate madness. I don’t think people should support premium games that give a clear round-winning advantage to those who spend more, play a different game instead.

I’ve never bought a loot crate in any game.  PUBG’s cosmetic crates don’t bother me.  I just sell the ones I naturally earn through gameplay via the Steam store. I’ve made $40+ US  doing that so hey, it paid for the game. <shrug>

PUBG Bugs and technical considerations

Nothing is perfect.  Cheating is rampant.  To give you an idea, over 1.5 MILLION accounts have been banned from PUBG. (that’s $45M in purchased copies, it’s insane)

I suspect the recent rubber-banding issues were from new anti-hack security, the more accurately you want to check and verify player actions, the slower the server gets. (I have a lot of experience with this…)

I play on the KR/JPN servers and latency is often an issue.  PUBG does not give us any in-game tools to clearly check our latency which is a bummer because it DOES matter when resolving “who shot first”.

The future

People were ready for a game mode that cut out the fluff and just presented the meat.  As usual, after a hit like this, over saturation will occur and soon enough, we’ll be ready for the next thing…

How to do your Unity builds in the background

If you’ve looked at my recent Unity-related posts and downloaded the projects, you might have noticed I have .bat files like CreateAndUploadWebGLBuild.bat in there to cleanly create final versions easily.

Great.  But if you run this .bat file while you are working on the game with the Unity editor, you’ll get this error:

Aborting batchmode due to failure:
Fatal Error! It looks like another Unity instance is running with this project open.

Multiple Unity instances cannot open the same project.

Ugh.  WebGL builds are especially are incredible slow, so this is a big productivity waster if you’re doing a lot of WebGL testing.  (If you work at a big company and waiting for builds is your favorite time to make coffee and catch up on reddit, well, close this page right now and hope your boss never reads this!)

Cloud Build?

Maybe you could use Unity’s Cloud Build but there are some down sides:

  • Cost $9 a month?
  • Requires that you commit each change to a cvs such as svn, perforce, or git
  • I doubt it can do custom post build commands such as code signing, building a final installer, or rscying files to a linux server and restarting the process.  I guess you could do those things yourself when the build is done, but heck, why not just handle the build yourself from the start.
  • Requires that all your assets are also on cvs (?)

The DIY way

So let’s do it old school with … yep, you guessed it… even more .bat files!  The secret is very simple, Unity will allow you to build in the background if the project directory is different.

You just need to copy your entire project to a temporary folder, then run Unity.exe with parms to do a headless build like normal.

So when you are in a good place with your project and would like to start a background build, hit Ctrl-S to save, then run your “CopyToTempDirAndBuild.bat” file.

After a couple seconds the initial copy is done and it’s safe to continue working while the build is happening in the temp directory – any changes will not be in the temp directory, so your build in progress won’t be affected.  So you can keep working away, without ever closing your main unity editor window.

It’s not especially tricky to do, but here are some .bat files to look at as an example that could be tweaked.

To copy a directory tree to a temp dir: (it assumes the .bat is run from the directory that’s going to be copied)


:this sets some info about the project, for example, it causes %APP_NAME% to hold our main directory name
call app_info_setup.bat

rmdir ..\%APP_NAME%Temp%1 /S /Q
echo Cloning %APP_NAME% to temp dir %APP_NAME%Temp%1...
mkdir ..\%APP_NAME%Temp%1
xcopy . ..\%APP_NAME%Temp%1\ /E /F /Y /EXCLUDE:%cd%\CloneExclusionList.txt

Note:  You may wonder why I’m being repetitive and using “Temp” everywhere instead of including it in a variable.  It’s because you NEVER, NEVER use things like rmdir with only a variable if you can help it, because at some point, that variable is going to be set incorrectly.  It might be  .. or / or something and you’ll delete your whole hard drive.  Safety first.

Another note: %cd% is a DOS trick to get the current full directory

CloneExclusionList.txt contains dirs we don’t want to waste time copying:



:this sets some info about the project, for example, it causes %APP_NAME% to hold our main directory name
call app_info_setup.bat
:Setting no pause causes our .bat files to not do pause commands when they are done
set NO_PAUSE=1
:First, let's customize the directory name we're going to close to, by adding a postfix to make it unique
:Now let's actually make it, we'll pass in the postfix as a parm
call CloneToTempDir.bat %CLONE_DIR_POSTFIX%

:Move to build dir
:Do the actual build
call BuildWebGL.bat
call UploadWebGLRsync.bat
:Move back out of it
cd ..
:Delete the temp dir we were just using

This calls CloneToTempDir.bat with the parm “WebGL” which gets appended to the <AppName>Temp dir.

It then “calls” (this means run another .bat, and come back when it’s done) .bats to create the webgl build and also upload it to the website.

It then destroys the temp directory completely, a good idea because Unity will mark it as the last project and you don’t want to accidently work on that directory later.

Parallel Builds

If you’ve got 16 threads sitting around like I do, it might make sense to build MORE THAN ONE version at a time. (now you see why I use a custom temp dir name for each build)

Apparently, Unity doesn’t care how many simultaneous builds you’re doing on a single computer, as long as its license is valid. (I’m using a pro license)

The key to running parallel builds is to use the “start” command instead of “call”. This means “run this, but instead of waiting, just continue running the rest of this .bat file”.


start BuildAndUploadWebGLInClonedDir.bat
start BuildAndUploadLinux64InClonedDir.bat

So, including the Unity editor you have open, when this is run you’ll have THREE instances of Unity running on the same computer at once.  It all works fine!

Continuous integration as a background operation on the same computer you develop on

If you add a “goto :start” at the bottom of your .bat and a “:start” label at the top, you can “clone and build” non-stop all day.  I don’t see this as very useful as it’s going to break all the time as we’re not doing controlled commits with a cvs but I thought I’d throw that there.