Shadowsocks via websocket through CloudFlare

Setting up one of these is a real pain in the neck. You follow some guide on the Internet and it never works. This one won't either, but hopefully it'll get you somewhere closer to success.

Starting with setting up the server, I chose the CentOS 8 image because ENTERPRISE, so I ended up with outdated packages. With GCP, you first need to allocate a static external ip address and SSH keys via the web console. GCP has its own firewall, so things like netcat also requires additional rules. Make sure to allow http and https ingress by tagging the instance with http and https. Additionally, don't be an idiot and associate the SSH key with the wrong username (the part before the @).

Onto the machine. Install docker(podman), and since we're using CentOS, everything is outdated, and it doesn't even have Caddy.

sudo yum install docker
curl https://getcaddy.com | bash -s personal tls.dns.cloudflare

Now, set up everything related to Caddy

sudo adduser -r -d /var/www -s /sbin/nologin caddy
mkdir /var/www
sudo mkdir /var/www
sudo mkdir /etc/caddy
sudo chown -R root:caddy /etc/caddy
sudo touch /etc/caddy/Caddyfile
sudo mkdir /etc/ssl/caddy
sudo chown -R caddy:root /etc/ssl/caddy
sudo chmod 770 /etc/ssl/caddy
sudo chown caddy:caddy /var/www
sudo curl -s https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service -o /etc/systemd/system/caddy.service

Thank you Digitalocean for providing a guide on Caddy. Now edit /etc/systemd/system/caddy.service and change the user and group to caddy. We may also uncomment the commented out options without knowing what they do.

At this point, we move on to setting up firewalld on our GCP vm instance.

sudo firewall-cmd --permanent --zone=public --add-interface=eth0
sudo firewall-cmd --set-default-zone=public
sudo firewall-cmd --permanent --add-port=22/tcp
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --zone=trusted --add-interface=docker0
sudo firewall-cmd --permanent --zone=public --add-masquerade

And verify that everything is setup

sudo firewall-cmd --permanent --list-all-zones

Then reload our firewalld config

sudo firewall-cmd --reload

Now that most things are ready to go, we can work on our actual shadowsocks server. Have the following files ready:

/etc/caddy/Caddyfile
# make sure to have an A record in cloudflare for this domain name
# with the proxy light on
my.domain.name {
  proxy /ws 127.0.0.1:10001 {
    without /ws
    websocket
    header_upstream -Origin
  }
  tls {
    dns cloudflare
  }
}

~/start.sh
#!/bin/bash
docker run \
    -d \
    --dns 8.8.8.8 \
    --dns 8.8.4.4 \
    -p 127.0.0.1:10001:1080 \
    -e PASSWORD=super-amazing-password-here \
    -e ARGS='-v -d 8.8.8.8' \
    --name ss \
    mazy/ss-v2ray:v3.3.3-1.2.0

/etc/systemd/system/caddy.service 
# to make caddy use the letsencrypt staging server
# add the following arguments to the arguments passed to the caddy binary
# so that if we mess up our dns configuration, letsencrypt won't throttle us
-ca https://acme-staging-v02.api.letsencrypt.org/directory

# add the following environment variables
# the global api key can be found under My Profile > API Tokens
Environment=CLOUDFLARE_EMAIL=MY_CLOUDFLARE_EMAIL
Environment=CLOUDFLARE_API_KEY=GLOBAL_API_KEY

Finally, let's start Caddy

sudo systemctl daemon-reload
sudo systemctl enable caddy
sudo systemctl start caddy

After a bit, let's check on it with

sudo systemctl status caddy

If everything is good, we can remove the staging server argument added earlier, restart caddy, and proceed to start our docker container with

sudo ~/start.sh

And we can check on it with

sudo docker logs ss

I copied my beautiful client configuration from the docker image github page:

{
    "server":"my.domain.name",
    "server_port":443,
    "local_address":"0.0.0.0",
    "local_port":1080,
    "password":"that-password-you-entered-earlier",
    "method":"chacha20-ietf-poly1305",
    "fast_open":true,
    "plugin":"/usr/local/bin/v2ray-plugin",
    "plugin_opts":"path=/ws;host=my.domain.name;tls"
}

Which I lovingly run with

ss-local -v -c ./ss.json

Optionally, privoxy config here:

listen-address 127.0.0.1:8118
forward-socks5 / 127.0.0.1:1080 .

Issues:

  1. If caddy fails to start with 203 Permission Denied, it might be SELinux. I don't know how to use it, but this command resets its context, whatever that means.
sudo restorecon -v /usr/local/bin/caddy