13

I am trying to connect to Redis with predis 1.1 and SSL, using information https://github.com/nrk/predis, where in the example the following configuration is used:

// Named array of connection parameters:
$client = new Predis\Client([
  'scheme' => 'tls',
  'ssl'    => ['cafile' => 'private.pem', 'verify_peer' => true],
]);

My Laravel configuration looks like below:

'redis' => [
        'client' => 'predis',
        'cluster' => env('REDIS_CLUSTER', false),

        'default' => [
            'host' => env('REDIS_HOST', 'localhost'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
        ],

        'options' => [
            'cluster' => 'redis',
            'parameters' => ['password' => env('REDIS_PASSWORD', null)],
            'scheme' => 'tls',
        ],
    ],

Unfortunately I am getting the following error:

ConnectionException in AbstractConnection.php line 155:
Error while reading line from the server. [tcp://MY_REDIS_SERVER_URL:6380]

Suggestions are appreciated :)

miken32
  • 42,008
  • 16
  • 111
  • 154
Lech Migdal
  • 3,828
  • 5
  • 36
  • 63

4 Answers4

32

I was able to get it to work!

You need to move 'scheme' from 'options' to 'default':

My working config:

'redis' => [
    'client' => 'predis',
    'cluster' => env('REDIS_CLUSTER', false),

    'default' => [
        'scheme' => 'tls',
        'host' => env('REDIS_HOST', 'localhost'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => 0,
    ],

    'options' => [
        'parameters' => ['password' => env('REDIS_PASSWORD', null)],
    ],
],

Note: I had also removed the 'cluster' option from 'options', but I don't suspect this to be the make-or-break with this problem.

In my final-final config, I changed it to: 'scheme' => env('REDIS_SCHEME', 'tcp'), and then defined REDIS_SCHEME=tls in my env file instead.

Tested with AWS ElastiCache with TLS enabled.

Edit: The above config only works with single-node redis. If you happen to enable clustering and TLS then you'll need a different config entirely.

'redis' => [
        'client' => 'predis',
        'cluster' => env('REDIS_CLUSTER', false),

        // Note! for single redis nodes, the default is defined here.
        // keeping it here for clusters will actually prevent the cluster config
        // from being used, it'll assume single node only.
        //'default' => [
        //    ...
        //],

        // #pro-tip, you can use the Cluster config even for single instances!
        'clusters' => [
            'default' => [
                [
                    'scheme'   => env('REDIS_SCHEME', 'tcp'),
                    'host'     => env('REDIS_HOST', 'localhost'),
                    'password' => env('REDIS_PASSWORD', null),
                    'port'     => env('REDIS_PORT', 6379),
                    'database' => env('REDIS_DATABASE', 0),
                ],
            ],
            'options' => [ // Clustering specific options
                'cluster' => 'redis', // This tells Redis Client lib to follow redirects (from cluster)
            ]
        ],
        'options' => [
            'parameters' => [ // Parameters provide defaults for the Connection Factory
                'password' => env('REDIS_PASSWORD', null), // Redirects need PW for the other nodes
                'scheme'   => env('REDIS_SCHEME', 'tcp'),  // Redirects also must match scheme
            ],
        ]
    ]

Explaining the above:

  • 'client' => 'predis': This specifies the PHP Library Redis driver to use (predis).
  • 'cluster' => 'redis': This tells Predis to assume server-side clustering. Which just means "follow redirects" (e.g. -MOVED responses). When running with a cluster, a node will respond with a -MOVED to the node that you must ask for a specific key.
  • If you don't have this enabled with Redis Clusters, Laravel will throw a -MOVED exception 1/n times, n being the number of nodes in Redis cluster (it'll get lucky and ask the right node every once in awhile)
  • 'clusters' => [...] : Specifies a list of nodes, but setting just a 'default' and pointing it to the AWS 'Configuration endpoint' will let it find any/all other nodes dynamically (recommended for Elasticache, because you don't know when nodes are comin' or goin').
  • 'options': For Laravel, can be specified at the top-level, cluster-level, and node option. (they get combined in Illuminate before being passed off to Predis)
  • 'parameters': These 'override' the default connection settings/assumptions that Predis uses for new connections. Since we set them explicitly for the 'default' connection, these aren't used. But for a cluster setup, they are critical. A 'master' node may send back a redirect (-MOVED) and unless the parameters are set for password and scheme it'll assume defaults, and that new connection to the new node will fail.
miken32
  • 42,008
  • 16
  • 111
  • 154
CenterOrbit
  • 6,446
  • 1
  • 28
  • 34
  • 1
    Excellent breakdown! Somebody correct me if I'm wrong but after looking through the Laravel 5.5 source code, the top level `'cluster' => env('REDIS_CLUSTER', false),` does nothing. To use clustering, you just have to have a top-level `clusters` array and, like @CenterOrbit mentions, **remove the top-level `default`** connection config. If Laravel finds a top-level key with the name of your connection (`default` by default), it won't look for the connection in the `clusters` config. https://github.com/laravel/framework/blob/5.5/src/Illuminate/Redis/RedisManager.php#L72 – Brandon Aug 28 '18 at 22:04
  • Also per-cluster `options` that @CenterOrbit mentions requires you to unintuitively mix in an associative `options` value into your non-associative array of nodes, e.g.: `'redis' => [ 'clusters' => [ 'default' => [ 'options' => [ 'cluster' => 'redis' ], [ 'scheme' => env('REDIS_SCHEME', 'tcp'), /* ... */ ], ], ], ]` – Brandon Aug 28 '18 at 22:05
  • [A gist](https://gist.github.com/bericp1/f6f5ec3a59062ab2d6f2abbebb0a3b2c) with better formatting and more info related to [my previous comment](https://stackoverflow.com/questions/41762751/laravel-redis-cache-via-ssl/48876398#comment91083956_48876398). – Brandon Aug 28 '18 at 22:17
4

Thank you CenterOrbit!!

I can confirm the first solution does allow Laravel to connect to a Redis server over TLS. Tested with Redis 3.2.6 on AWS ElastiCache with TLS, configured as single node and single shard.

I can also confirm the second solution does allow Laravel to connect to a Redis Cluster over TLS. Tested with Redis 3.2.6 on AWS ElastiCache with TLS, configured with "Cluster Mode Enabled", 1 shard, 1 replica per shard.

I was receiving the following error when I first tried to implement the cluster solution:

Error: Unsupported operand types

I missed the additional set of array brackets when I moved the "default" settings into the "clusters" array.

INCORRECT

'clusters' => [
  'default' => [
    'scheme' ...
  ]
]

CORRECT

'clusters' => [
  'default' => [
    [
      'scheme' ...
    ]
  ]
]

I hope this saves someone else a bit of troubleshooting time.

Jason Klein
  • 79
  • 1
  • 3
0

The accepted solution by CenterOrbit worked for me, as I was using AWS I had to add tls:// in my .env Laravel

0

tls://username:password@URL:PORT?database=0 Try it. It will work

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 15 '22 at 13:51