Craft 3 Plugin Routing Problems
EDIT:
I'm so sorry - I messed it up... I rechecked the craft code and I was all the way wrong with the first answer. I hope you had no trouble because of me.
The correct answer is this one....
Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function(RegisterUrlRulesEvent $event) {
    $event-&gt;rules['redirect/&lt;action:\w+&gt;'] = 'customer-portal/portal/';
    $event-&gt;rules['checkout']              = 'customer-portal/portal/checkout';
});

and in your controller
public function actionIndex(){
    $request = Craft::$app-&gt;getRequest();
    $method = $request-&gt;getParam('action');
    return $this-&gt;redirect($method);
}

In order to have access to the backend function you need to make it &quot;public&quot; so you have to include a protected $allowAnonymous = true; or an array with all functions protected $allowAnonymous = ['functionOne', 'functionTwo'];
Your Controller could look like this
class UserController extends Controller {
    protected $allowAnonymous = true;

    // pretend the request is /redirect/foo and we have created the rule 
    // $event-&gt;rules['foo']                    = 'customer-portal/user/foo'; and
    // $event-&gt;rules['redirect/&lt;action:\w+&gt;']  = 'customer-portal/user/redirect';
    public function actionRedirect(){
        $request = Craft::$app-&gt;getRequest();
        $method = $request-&gt;getParam('action');
        return $this-&gt;redirect($method);
    }

    public function actionFoo(){
        die(&quot;foo&quot;);
    }

}

Old Post... ignore this
It's not a bug, that's the way how craft handles it. Unlike in other cms plugins in craft are not supposed to handle entire frontend page requests in order to render the entire page.
You can access actions from controllers only via the /action url. Everything else is handled by entries/elements
So you'll not be able to achieve what you want since craft tries to access an entry when you don't include an action url. All frontend request without an action url need to be related to an entry/element or a category. So when you try to access /redirect craft will search for an entry with the slug redirect
What you want is the following:

creating an entry with the slug &quot;redirect&quot;
$entry = Craft::$app-&gt; //your logic to fetch this specific entry
$this-&gt;redirect($entry-&gt;url)

When you want to create a plugin for other people to use it, you have to create a settings page, include a relation field to that and let other people link an entry to the redirect -&gt; you can fetch this entry from your settings and redirect to it.
The other thing... $event-&gt;rules['portal/&lt;action&gt;'] = 'customer-portal/portal/&lt;action&gt;'; is wrong.. Take a look at this documentation
$event-&gt;rules['portal/&lt;action:\w+&gt;'] = 'customer-portal/portal/handle-redirect';
and in your portalController
public function actionHandleRedirect(){
    $request = Craft::$app-&gt;getRequest();
    $function = &quot;action&quot; . $request-&gt;getParam('action');
    $this-&gt;$function();
}

But I would suggest you not to do that and use the craft way to handle these things